From dd6d413ff1a5d19b79ef5a91cabe3fe2a6ee5ae4 Mon Sep 17 00:00:00 2001 From: Alex Dukhno <5074607+alex-dukhno@users.noreply.github.com> Date: Sun, 13 Sep 2020 22:32:34 +0300 Subject: [PATCH] type constraints validate ScalarValue and return valid Datum (#316) --- Cargo.lock | 55 +- Cargo.toml | 1 + src/ast/src/lib.rs | 101 +--- src/constraints/Cargo.toml | 15 + src/constraints/src/lib.rs | 630 ++++++++++++++++++++++ src/query_planner/Cargo.toml | 1 + src/query_planner/src/plan.rs | 5 +- src/query_planner/src/planner/insert.rs | 18 +- src/query_planner/src/planner/update.rs | 8 +- src/query_planner/src/tests/insert.rs | 23 +- src/query_planner/src/tests/update.rs | 8 +- src/sql_engine/Cargo.toml | 1 + src/sql_engine/src/dml/insert.rs | 27 +- src/sql_engine/src/dml/update.rs | 22 +- src/sql_model/Cargo.toml | 1 - src/sql_model/src/sql_types.rs | 688 +----------------------- 16 files changed, 738 insertions(+), 866 deletions(-) create mode 100644 src/constraints/Cargo.toml create mode 100644 src/constraints/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 296fe958..87cae8ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,12 +21,6 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" - [[package]] name = "ast" version = "0.1.0" @@ -338,6 +332,17 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "constraints" +version = "0.1.0" +dependencies = [ + "ast", + "bigdecimal", + "num-bigint", + "rstest", + "sql_model", +] + [[package]] name = "core-foundation" version = "0.9.0" @@ -612,29 +617,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lexical" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ff8a57641758c89a37b2d28556a68978b3ea3f709f39953e153acea3dbacf" -dependencies = [ - "cfg-if", - "lexical-core", -] - -[[package]] -name = "lexical-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.77" @@ -951,6 +933,7 @@ name = "query_planner" version = "0.1.0" dependencies = [ "ast", + "constraints", "data_manager", "kernel", "protocol", @@ -1052,12 +1035,6 @@ dependencies = [ "semver", ] -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - [[package]] name = "schannel" version = "0.1.19" @@ -1238,6 +1215,7 @@ dependencies = [ "bigdecimal", "binary", "bincode", + "constraints", "data_manager", "expr_eval", "futures-lite", @@ -1256,7 +1234,6 @@ dependencies = [ name = "sql_model" version = "0.1.0" dependencies = [ - "lexical", "protocol", "rstest", "serde", @@ -1273,12 +1250,6 @@ dependencies = [ "log", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "syn" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index cb67f8c7..71e3ff55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "src/ast", "src/binary", + "src/constraints", "src/data_manager", "src/expr_eval", "src/kernel", diff --git a/src/ast/src/lib.rs b/src/ast/src/lib.rs index 39696ec6..ab80fdb4 100644 --- a/src/ast/src/lib.rs +++ b/src/ast/src/lib.rs @@ -141,11 +141,11 @@ impl<'a> Datum<'a> { } pub fn from_f32(val: f32) -> Datum<'static> { - Datum::Float32(val.into()) + Datum::Float32(OrderedFloat(val)) } pub fn from_f64(val: f64) -> Datum<'static> { - Datum::Float64(val.into()) + Datum::Float64(OrderedFloat(val)) } #[allow(clippy::should_implement_trait)] @@ -161,28 +161,6 @@ impl<'a> Datum<'a> { Datum::SqlType(val) } - // @TODO: Add accessor helper functions. - pub fn as_i16(&self) -> i16 { - match self { - Self::Int16(val) => *val, - _ => panic!("invalid use of Datum::as_i16"), - } - } - - pub fn as_i32(&self) -> i32 { - match self { - Self::Int32(val) => *val, - _ => panic!("invalid use of Datum::as_i32"), - } - } - - pub fn as_i64(&self) -> i64 { - match self { - Self::Int64(val) => *val, - _ => panic!("invalid use of Datum::as_i64"), - } - } - pub fn as_u64(&self) -> u64 { match self { Self::UInt64(val) => *val, @@ -190,28 +168,6 @@ impl<'a> Datum<'a> { } } - pub fn as_f32(&self) -> f32 { - match self { - Self::Float32(val) => **val, - _ => panic!("invalid use of Datum::as_f32"), - } - } - - pub fn as_f64(&self) -> f64 { - match self { - Self::Float64(val) => **val, - _ => panic!("invalid use of Datum::as_f64"), - } - } - - pub fn as_bool(&self) -> bool { - match self { - Self::True => true, - Self::False => false, - _ => panic!("invalid use of Datum::as_bool"), - } - } - pub fn as_str(&self) -> &str { match self { Self::String(s) => s, @@ -219,65 +175,12 @@ impl<'a> Datum<'a> { } } - pub fn as_string(&self) -> &str { - match self { - Self::OwnedString(s) => s, - _ => panic!("invalid use of Datum::as_string"), - } - } - pub fn as_sql_type(&self) -> SqlType { match self { Self::SqlType(sql_type) => *sql_type, _ => panic!("invalid use of Datum::as_sql_type"), } } - - pub fn is_integer(&self) -> bool { - match self { - Self::Int16(_) | Self::Int32(_) | Self::Int64(_) => true, - _ => false, - } - } - - pub fn is_float(&self) -> bool { - match self { - Self::Float32(_) | Self::Float64(_) => true, - _ => false, - } - } - - pub fn is_string(&self) -> bool { - match self { - Self::String(_) | Self::OwnedString(_) => true, - _ => false, - } - } - - pub fn is_boolean(&self) -> bool { - match self { - Self::True | Self::False => true, - _ => false, - } - } - - pub fn is_null(&self) -> bool { - if let Self::Null = self { - true - } else { - false - } - } - - pub fn is_type(&self) -> bool { - if let Self::SqlType(_) = self { - true - } else { - false - } - } - - // arithmetic operations } #[derive(Debug, Clone)] diff --git a/src/constraints/Cargo.toml b/src/constraints/Cargo.toml new file mode 100644 index 00000000..88e7ef84 --- /dev/null +++ b/src/constraints/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "constraints" +version = "0.1.0" +authors = ["Alex Dukhno "] +edition = "2018" +publish = false + +[dependencies] +ast = { path = "../ast" } +sql_model = { path = "../sql_model" } +bigdecimal = "0.1.2" +num-bigint = "0.2" + +[dev-dependencies] +rstest = "0.6.4" diff --git a/src/constraints/src/lib.rs b/src/constraints/src/lib.rs new file mode 100644 index 00000000..cbe2afee --- /dev/null +++ b/src/constraints/src/lib.rs @@ -0,0 +1,630 @@ +// Copyright 2020 Alex Dukhno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use ast::{ + values::{Bool, ScalarValue}, + Datum, +}; +use bigdecimal::{BigDecimal, ToPrimitive}; +use num_bigint::BigInt; +use sql_model::sql_types::SqlType; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ConstraintError { + OutOfRange, + TypeMismatch(String), + ValueTooLong(u64), +} + +pub trait Constraint { + fn validate(&self, in_value: ScalarValue) -> Result; +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TypeConstraint { + Bool, + Char(u64), + VarChar(u64), + SmallInt(i16), + Integer(i32), + BigInt(i64), + Real, + DoublePrecision, +} + +impl From<&SqlType> for TypeConstraint { + fn from(sql_type: &SqlType) -> TypeConstraint { + match sql_type { + SqlType::Bool => TypeConstraint::Bool, + SqlType::Char(len) => TypeConstraint::Char(*len), + SqlType::VarChar(len) => TypeConstraint::VarChar(*len), + SqlType::SmallInt(min) => TypeConstraint::SmallInt(*min), + SqlType::Integer(min) => TypeConstraint::Integer(*min), + SqlType::BigInt(min) => TypeConstraint::BigInt(*min), + SqlType::Real => TypeConstraint::Real, + SqlType::DoublePrecision => TypeConstraint::DoublePrecision, + } + } +} + +impl Constraint for TypeConstraint { + fn validate(&self, in_value: ScalarValue) -> Result { + match self { + TypeConstraint::SmallInt(min) => match &in_value { + ScalarValue::Number(value) => { + let (int, exp) = value.as_bigint_and_exponent(); + if exp != 0 { + Err(ConstraintError::TypeMismatch(in_value.to_string())) + } else if BigInt::from(*min) <= int && int <= BigInt::from(i16::max_value()) { + Ok(Datum::Int16(int.to_i16().unwrap())) + } else { + Err(ConstraintError::OutOfRange) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::Integer(min) => match &in_value { + ScalarValue::Number(value) => { + let (int, exp) = value.as_bigint_and_exponent(); + if exp != 0 { + Err(ConstraintError::TypeMismatch(in_value.to_string())) + } else if BigInt::from(*min) <= int && int <= BigInt::from(i32::max_value()) { + Ok(Datum::Int32(int.to_i32().unwrap())) + } else { + Err(ConstraintError::OutOfRange) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::BigInt(min) => match &in_value { + ScalarValue::Number(value) => { + let (int, exp) = value.as_bigint_and_exponent(); + if exp != 0 { + Err(ConstraintError::TypeMismatch(in_value.to_string())) + } else if BigInt::from(*min) <= int && int <= BigInt::from(i64::max_value()) { + Ok(Datum::Int64(int.to_i64().unwrap())) + } else { + Err(ConstraintError::OutOfRange) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::Char(len) => match &in_value { + ScalarValue::String(in_value) => { + let trimmed = in_value.trim_end(); + if trimmed.len() > *len as usize { + Err(ConstraintError::ValueTooLong(*len)) + } else { + Ok(Datum::OwnedString(trimmed.to_owned())) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::VarChar(len) => match &in_value { + ScalarValue::String(in_value) => { + let trimmed = in_value.trim_end(); + if trimmed.len() > *len as usize { + Err(ConstraintError::ValueTooLong(*len)) + } else { + Ok(Datum::OwnedString(trimmed.to_owned())) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::Bool => match &in_value { + ScalarValue::Bool(Bool(boolean)) => Ok(Datum::from_bool(*boolean)), + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::Real => match &in_value { + ScalarValue::Number(value) => { + if BigDecimal::from(f32::MIN) <= *value && *value <= BigDecimal::from(f32::MAX) { + Ok(Datum::from_f32(value.to_f32().unwrap())) + } else { + Err(ConstraintError::OutOfRange) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + TypeConstraint::DoublePrecision => match &in_value { + ScalarValue::Number(value) => { + if BigDecimal::from(f64::MIN) <= *value && *value <= BigDecimal::from(f64::MAX) { + Ok(Datum::from_f64(value.to_f64().unwrap())) + } else { + Err(ConstraintError::OutOfRange) + } + } + _ => Err(ConstraintError::TypeMismatch(in_value.to_string())), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ast::values::Bool; + use bigdecimal::BigDecimal; + use std::str::FromStr; + + #[cfg(test)] + mod ints { + use super::*; + + #[cfg(test)] + mod small { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::SmallInt(i16::min_value()) + } + + #[rstest::rstest] + fn in_range(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("1").unwrap())), + Ok(Datum::Int16(1)) + ); + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("32767").unwrap())), + Ok(Datum::Int16(i16::max_value())) + ); + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-32768").unwrap())), + Ok(Datum::Int16(i16::min_value())) + ); + } + + #[rstest::rstest] + fn greater_than_max(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("32769").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn less_than_min(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-32769").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn a_float_number(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-3276.9").unwrap())), + Err(ConstraintError::TypeMismatch("-3276.9".to_owned())) + ) + } + + #[rstest::rstest] + fn a_string(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("str".to_owned())), + Err(ConstraintError::TypeMismatch("str".to_owned())) + ) + } + + #[test] + fn min_bound() { + let constraint = TypeConstraint::SmallInt(0); + + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-1").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + } + } + + #[cfg(test)] + mod integer { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::Integer(i32::min_value()) + } + + #[rstest::rstest] + fn in_range(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("1").unwrap())), + Ok(Datum::Int32(1)) + ); + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-2147483648").unwrap())), + Ok(Datum::Int32(i32::min_value())) + ); + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("2147483647").unwrap())), + Ok(Datum::Int32(i32::max_value())) + ); + } + + #[rstest::rstest] + fn greater_than_max(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("2147483649").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn less_than_min(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-2147483649").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn a_float_number(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-214748.3649").unwrap())), + Err(ConstraintError::TypeMismatch("-214748.3649".to_owned())) + ) + } + + #[rstest::rstest] + fn a_string(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("str".to_string())), + Err(ConstraintError::TypeMismatch("str".to_owned())) + ) + } + + #[test] + fn min_bound() { + let constraint = TypeConstraint::Integer(0); + + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-1").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + } + } + + #[cfg(test)] + mod big_int { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::BigInt(i64::min_value()) + } + + #[rstest::rstest] + fn in_range(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("1").unwrap())), + Ok(Datum::Int64(1)) + ); + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str("-9223372036854775808").unwrap() + )), + Ok(Datum::Int64(i64::min_value())) + ); + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str("9223372036854775807").unwrap() + )), + Ok(Datum::Int64(i64::max_value())) + ); + } + + #[rstest::rstest] + fn greater_than_max(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str("9223372036854775809").unwrap() + )), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn less_than_min(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str("-9223372036854775809").unwrap() + )), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn a_float_number(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-3276.9").unwrap())), + Err(ConstraintError::TypeMismatch("-3276.9".to_owned())) + ) + } + + #[rstest::rstest] + fn a_string(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("str".to_owned())), + Err(ConstraintError::TypeMismatch("str".to_owned())) + ) + } + + #[test] + fn min_bound() { + let constraint = TypeConstraint::BigInt(0); + + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("-1").unwrap())), + Err(ConstraintError::OutOfRange) + ) + } + } + } + } + + #[cfg(test)] + mod strings { + use super::*; + + #[cfg(test)] + mod chars { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::Char(10) + } + + #[rstest::rstest] + fn in_length(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("1".to_owned())), + Ok(Datum::OwnedString("1".to_owned())) + ) + } + + #[rstest::rstest] + fn too_long(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("1".repeat(20))), + Err(ConstraintError::ValueTooLong(10)) + ) + } + } + } + + #[cfg(test)] + mod var_chars { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::VarChar(10) + } + + #[rstest::rstest] + fn in_length(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("1".to_owned())), + Ok(Datum::OwnedString("1".to_owned())) + ) + } + + #[rstest::rstest] + fn too_long(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("1".repeat(20))), + Err(ConstraintError::ValueTooLong(10)) + ) + } + } + } + } + + #[cfg(test)] + mod bool { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::Bool + } + + #[rstest::rstest] + fn is_ok_true(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Bool(Bool(true))), + Ok(Datum::from_bool(true)) + ); + } + + #[rstest::rstest] + fn is_ok_false(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Bool(Bool(false))), + Ok(Datum::from_bool(false)) + ); + } + + #[rstest::rstest] + fn is_non_bool(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("oops".to_owned())), + Err(ConstraintError::TypeMismatch("oops".to_owned())) + ) + } + } + } + + #[cfg(test)] + mod floats { + use super::*; + + #[cfg(test)] + mod real { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::Real + } + + #[rstest::rstest] + fn in_range(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("1").unwrap())), + Ok(Datum::from_f32(1.0)) + ); + //TODO investigate floating point conversion + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from( + f32::MAX - 50000000000000000000000000000000.0 + ))), + Ok(Datum::from_f32(f32::MAX - 50000000000000000000000000000000.0)) + ); + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from( + f32::MIN + 50000000000000000000000000000000.0 + ))), + Ok(Datum::from_f32(f32::MIN + 50000000000000000000000000000000.0)) + ); + } + + #[rstest::rstest] + fn greater_than_max(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str(&(f32::MAX.to_string() + "1")).unwrap() + )), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn less_than_min(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str(&(f32::MIN.to_string() + "1")).unwrap() + )), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn a_string(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("str".to_owned())), + Err(ConstraintError::TypeMismatch("str".to_owned())) + ) + } + } + } + + #[cfg(test)] + mod double_precision { + use super::*; + + #[cfg(test)] + mod validation { + use super::*; + + #[rstest::fixture] + fn constraint() -> TypeConstraint { + TypeConstraint::DoublePrecision + } + + #[rstest::rstest] + fn in_range(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number(BigDecimal::from_str("1").unwrap())), + Ok(Datum::from_f64(1.0)) + ); + //TODO investigate floating point conversion + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str(&f64::MAX.to_string()).unwrap() + )), + Ok(Datum::from_f64(f64::MAX)) + ); + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str(&f64::MIN.to_string()).unwrap() + )), + Ok(Datum::from_f64(f64::MIN)) + ); + } + + #[rstest::rstest] + fn greater_than_max(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str(&(f64::MAX.to_string() + "1")).unwrap() + )), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn less_than_min(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::Number( + BigDecimal::from_str(&(f64::MIN.to_string() + "1")).unwrap() + )), + Err(ConstraintError::OutOfRange) + ) + } + + #[rstest::rstest] + fn a_string(constraint: TypeConstraint) { + assert_eq!( + constraint.validate(ScalarValue::String("str".to_owned())), + Err(ConstraintError::TypeMismatch("str".to_owned())) + ) + } + } + } + } +} diff --git a/src/query_planner/Cargo.toml b/src/query_planner/Cargo.toml index afc40f10..84369637 100644 --- a/src/query_planner/Cargo.toml +++ b/src/query_planner/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] ast = { path = "../ast" } +constraints = { path = "../constraints" } sqlparser = { version = "0.6.1", features = ["bigdecimal"] } sql_model = { path = "../sql_model" } data_manager = { path = "../data_manager" } diff --git a/src/query_planner/src/plan.rs b/src/query_planner/src/plan.rs index e0840a9c..7d0b60c6 100644 --- a/src/query_planner/src/plan.rs +++ b/src/query_planner/src/plan.rs @@ -15,6 +15,7 @@ ///! represents a plan to be executed by the engine. use crate::{SchemaId, TableId}; use ast::operations::ScalarOp; +use constraints::TypeConstraint; use data_manager::ColumnDefinition; use sql_model::{sql_types::SqlType, Id}; use sqlparser::ast::Statement; @@ -56,14 +57,14 @@ impl SchemaCreationInfo { #[derive(PartialEq, Debug, Clone)] pub struct TableInserts { pub table_id: TableId, - pub column_indices: Vec<(usize, String, SqlType)>, + pub column_indices: Vec<(usize, String, SqlType, TypeConstraint)>, pub input: Vec>, } #[derive(PartialEq, Debug, Clone)] pub struct TableUpdates { pub table_id: TableId, - pub column_indices: Vec<(usize, String, SqlType)>, + pub column_indices: Vec<(usize, String, SqlType, TypeConstraint)>, pub input: Vec, } diff --git a/src/query_planner/src/planner/insert.rs b/src/query_planner/src/planner/insert.rs index bb7e5c81..d353416f 100644 --- a/src/query_planner/src/planner/insert.rs +++ b/src/query_planner/src/planner/insert.rs @@ -18,6 +18,7 @@ use crate::{ FullTableName, TableId, }; use ast::operations::ScalarOp; +use constraints::TypeConstraint; use data_manager::DataManager; use protocol::{results::QueryError, Sender}; use sqlparser::ast::{Ident, ObjectName, Query, SetExpr}; @@ -93,7 +94,14 @@ impl Planner for InsertPlanner<'_> { .iter() .cloned() .enumerate() - .map(|(index, col_def)| (index, col_def.name(), col_def.sql_type())) + .map(|(index, col_def)| { + ( + index, + col_def.name(), + col_def.sql_type(), + TypeConstraint::from(&col_def.sql_type()), + ) + }) .collect::>() } else { let mut columns = HashSet::new(); @@ -110,8 +118,12 @@ impl Planner for InsertPlanner<'_> { has_error = true; } columns.insert(column_name.to_owned()); - found = - Some((index, column_name.to_owned(), column_definition.sql_type())); + found = Some(( + index, + column_name.to_owned(), + column_definition.sql_type(), + TypeConstraint::from(&column_definition.sql_type()), + )); break; } } diff --git a/src/query_planner/src/planner/update.rs b/src/query_planner/src/planner/update.rs index e0dcb153..d6c7fab6 100644 --- a/src/query_planner/src/planner/update.rs +++ b/src/query_planner/src/planner/update.rs @@ -18,6 +18,7 @@ use crate::{ FullTableName, TableId, }; use ast::operations::ScalarOp; +use constraints::TypeConstraint; use data_manager::DataManager; use protocol::{results::QueryError, Sender}; use sqlparser::ast::{Assignment, ObjectName}; @@ -95,7 +96,12 @@ impl Planner for UpdatePlanner<'_> { .expect("To Send Result to Client"); } columns.insert(column_name.clone()); - found = Some((index, column_definition.name(), column_definition.sql_type())); + found = Some(( + index, + column_definition.name(), + column_definition.sql_type(), + TypeConstraint::from(&column_definition.sql_type()), + )); break; } } diff --git a/src/query_planner/src/tests/insert.rs b/src/query_planner/src/tests/insert.rs index c1cc180d..0e2827e6 100644 --- a/src/query_planner/src/tests/insert.rs +++ b/src/query_planner/src/tests/insert.rs @@ -18,6 +18,7 @@ use crate::{ planner::QueryPlanner, tests::{ident, ResultCollector, TABLE}, }; +use constraints::TypeConstraint; use protocol::results::QueryError; use sqlparser::ast::{ObjectName, Query, SetExpr, Statement, Values}; @@ -140,9 +141,14 @@ fn insert_into_table(planner_and_sender_with_table: (QueryPlanner, ResultCollect Ok(Plan::Insert(TableInserts { table_id: TableId((0, 0)), column_indices: vec![ - (0, "small_int".to_owned(), SqlType::SmallInt(0)), - (1, "integer".to_owned(), SqlType::Integer(0)), - (2, "big_int".to_owned(), SqlType::BigInt(0)) + ( + 0, + "small_int".to_owned(), + SqlType::SmallInt(0), + TypeConstraint::SmallInt(0) + ), + (1, "integer".to_owned(), SqlType::Integer(0), TypeConstraint::Integer(0)), + (2, "big_int".to_owned(), SqlType::BigInt(0), TypeConstraint::BigInt(0)) ], input: vec![] })) @@ -170,9 +176,14 @@ fn insert_into_table_without_columns(planner_and_sender_with_table: (QueryPlanne Ok(Plan::Insert(TableInserts { table_id: TableId((0, 0)), column_indices: vec![ - (0, "small_int".to_owned(), SqlType::SmallInt(0)), - (1, "integer".to_owned(), SqlType::Integer(0)), - (2, "big_int".to_owned(), SqlType::BigInt(0)) + ( + 0, + "small_int".to_owned(), + SqlType::SmallInt(0), + TypeConstraint::SmallInt(0) + ), + (1, "integer".to_owned(), SqlType::Integer(0), TypeConstraint::Integer(0)), + (2, "big_int".to_owned(), SqlType::BigInt(0), TypeConstraint::BigInt(0)) ], input: vec![] })) diff --git a/src/query_planner/src/tests/update.rs b/src/query_planner/src/tests/update.rs index 4acff3e8..563ec6e4 100644 --- a/src/query_planner/src/tests/update.rs +++ b/src/query_planner/src/tests/update.rs @@ -19,6 +19,7 @@ use crate::{ tests::{ident, ResultCollector, TABLE}, }; use ast::{operations::ScalarOp, values::ScalarValue}; +use constraints::TypeConstraint; use protocol::results::QueryError; use sqlparser::ast::{Assignment, Expr, ObjectName, Statement, Value}; @@ -114,7 +115,12 @@ fn update_table(planner_and_sender_with_table: (QueryPlanner, ResultCollector)) }), Ok(Plan::Update(TableUpdates { table_id: TableId((0, 0)), - column_indices: vec![(0, "small_int".to_owned(), SqlType::SmallInt(0))], + column_indices: vec![( + 0, + "small_int".to_owned(), + SqlType::SmallInt(0), + TypeConstraint::SmallInt(0) + )], input: vec![ScalarOp::Value(ScalarValue::String("".to_string()))], })) ); diff --git a/src/sql_engine/Cargo.toml b/src/sql_engine/Cargo.toml index 07f31214..394e6501 100644 --- a/src/sql_engine/Cargo.toml +++ b/src/sql_engine/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] ast = { path = "../ast" } +constraints = { path = "../constraints" } data_manager = { path = "../data_manager" } expr_eval = { path = "../expr_eval" } log = "0.4.11" diff --git a/src/sql_engine/src/dml/insert.rs b/src/sql_engine/src/dml/insert.rs index dc9bc74a..26b16bc9 100644 --- a/src/sql_engine/src/dml/insert.rs +++ b/src/sql_engine/src/dml/insert.rs @@ -15,18 +15,17 @@ use std::sync::Arc; use binary::Binary; +use constraints::{Constraint, ConstraintError}; use data_manager::{ColumnDefinition, DataManager, Row}; use kernel::{SystemError, SystemResult}; use protocol::{ results::{QueryError, QueryEvent}, Sender, }; -use sql_model::sql_types::ConstraintError; -use ast::{operations::ScalarOp, Datum, EvalError}; +use ast::{operations::ScalarOp, Datum}; use expr_eval::static_expr::StaticExpressionEvaluation; use query_planner::plan::TableInserts; -use std::convert::TryFrom; pub(crate) struct InsertCommand { table_inserts: TableInserts, @@ -92,21 +91,17 @@ impl InsertCommand { // TODO: The default value or NULL should be initialized for SQL types of all columns. let mut record = vec![Datum::from_null(); self.table_inserts.column_indices.len()]; let mut errors = vec![]; - for (item, (index, name, sql_type)) in row.iter().zip(self.table_inserts.column_indices.iter()) { + for (item, (index, name, sql_type, type_constraint)) in + row.iter().zip(self.table_inserts.column_indices.iter()) + { match item.cast(sql_type) { - Ok(item) => match Datum::try_from(&item) { - Ok(datum) => match sql_type.constraint().validate(datum.to_string().as_str()) { - Ok(()) => { - record[*index] = datum; - } - Err(error) => { - errors.push((error, ColumnDefinition::new(name, *sql_type))); - } - }, - Err(EvalError::OutOfRangeNumeric(_)) => { - errors.push((ConstraintError::OutOfRange, ColumnDefinition::new(name, *sql_type))) + Ok(item) => match type_constraint.validate(item) { + Ok(datum) => { + record[*index] = datum; + } + Err(error) => { + errors.push((error, ColumnDefinition::new(name, *sql_type))); } - Err(_) => return Ok(()), }, Err(_err) => { self.sender diff --git a/src/sql_engine/src/dml/update.rs b/src/sql_engine/src/dml/update.rs index 0a11374d..e1f503e8 100644 --- a/src/sql_engine/src/dml/update.rs +++ b/src/sql_engine/src/dml/update.rs @@ -19,12 +19,12 @@ use data_manager::DataManager; use kernel::SystemResult; use protocol::Sender; -use ast::{operations::ScalarOp, Datum}; +use ast::operations::ScalarOp; +use constraints::{Constraint, ConstraintError}; use expr_eval::{dynamic_expr::DynamicExpressionEvaluation, static_expr::StaticExpressionEvaluation}; use protocol::results::{QueryError, QueryEvent}; use query_planner::plan::TableUpdates; -use sql_model::sql_types::ConstraintError; -use std::{collections::HashMap, convert::TryFrom}; +use std::collections::HashMap; pub(crate) struct UpdateCommand { table_update: TableUpdates, @@ -57,7 +57,7 @@ impl UpdateCommand { let mut assignments = vec![]; let mut has_error = false; - for ((index, column_name, scalar_type), item) in self + for ((index, column_name, sql_type, type_constraint), item) in self .table_update .column_indices .iter() @@ -65,7 +65,13 @@ impl UpdateCommand { { match evaluation.eval(item) { Ok(value) => { - assignments.push((column_name.clone(), *index, Box::new(value), *scalar_type)); + assignments.push(( + column_name.clone(), + *index, + Box::new(value), + *sql_type, + *type_constraint, + )); } Err(()) => { has_error = true; @@ -95,7 +101,7 @@ impl UpdateCommand { let mut has_err = false; for update in assignments.as_slice() { - let (column_name, destination, value, sql_type) = update; + let (column_name, destination, value, sql_type, type_constraint) = update; let value = match expr_eval.eval(data.as_slice(), value.as_ref()) { Ok(ScalarOp::Value(value)) => value, Ok(_) => return Ok(()), @@ -110,8 +116,8 @@ impl UpdateCommand { return Ok(()); } }; - match sql_type.constraint().validate(value.to_string().as_str()) { - Ok(()) => updated[*destination] = Datum::try_from(&value).expect("ok"), + match type_constraint.validate(value) { + Ok(datum) => updated[*destination] = datum, Err(ConstraintError::OutOfRange) => { self.sender .send(Err(QueryError::out_of_range(sql_type.into(), column_name, row_idx + 1))) diff --git a/src/sql_model/Cargo.toml b/src/sql_model/Cargo.toml index fac1f39a..81333119 100644 --- a/src/sql_model/Cargo.toml +++ b/src/sql_model/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" publish = false [dependencies] -lexical = "5.2.0" serde = { version = "1.0.115", features = ["derive"] } protocol = { path = "../protocol" } sqlparser = { version = "0.6.1", features = ["bigdecimal"] } diff --git a/src/sql_model/src/sql_types.rs b/src/sql_model/src/sql_types.rs index c5290198..5713cdf5 100644 --- a/src/sql_model/src/sql_types.rs +++ b/src/sql_model/src/sql_types.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use serde::{Deserialize, Serialize}; @@ -80,49 +80,6 @@ impl Display for SqlType { } } -impl SqlType { - pub fn validate_and_serialize(&self, value: &str) -> Result, ConstraintError> { - self.constraint().validate(value).map(|()| self.serializer().ser(value)) - } - - pub fn constraint(&self) -> Box { - match *self { - Self::Char(length) => Box::new(CharSqlTypeConstraint { length }), - Self::VarChar(length) => Box::new(VarCharSqlTypeConstraint { length }), - Self::SmallInt(min) => Box::new(SmallIntTypeConstraint { min }), - Self::Integer(min) => Box::new(IntegerSqlTypeConstraint { min }), - Self::BigInt(min) => Box::new(BigIntTypeConstraint { min }), - Self::Bool => Box::new(BoolSqlTypeConstraint), - sql_type => unimplemented!("Type constraint for {:?} is not currently implemented", sql_type), - } - } - - pub fn serializer(&self) -> Box { - match *self { - Self::Char(_length) => Box::new(CharSqlTypeSerializer), - Self::VarChar(_length) => Box::new(VarCharSqlTypeSerializer), - Self::SmallInt(_min) => Box::new(SmallIntTypeSerializer), - Self::Integer(_min) => Box::new(IntegerSqlTypeSerializer), - Self::BigInt(_min) => Box::new(BigIntTypeSerializer), - Self::Bool => Box::new(BoolSqlTypeSerializer), - sql_type => unimplemented!("Type Serializer for {:?} is not currently implemented", sql_type), - } - } - - pub fn to_pg_types(&self) -> PostgreSqlType { - match *self { - Self::Bool => PostgreSqlType::Bool, - Self::Char(_) => PostgreSqlType::Char, - Self::VarChar(_) => PostgreSqlType::VarChar, - Self::SmallInt(_) => PostgreSqlType::SmallInt, - Self::Integer(_) => PostgreSqlType::Integer, - Self::BigInt(_) => PostgreSqlType::BigInt, - Self::Real => PostgreSqlType::Real, - Self::DoublePrecision => PostgreSqlType::DoublePrecision, - } - } -} - impl Into for &SqlType { fn into(self) -> PostgreSqlType { match self { @@ -138,229 +95,6 @@ impl Into for &SqlType { } } -pub trait Constraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError>; -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ConstraintError { - OutOfRange, - TypeMismatch(String), - ValueTooLong(u64), -} - -pub trait Serializer { - fn ser(&self, in_value: &str) -> Vec; - - fn des(&self, out_value: &[u8]) -> String; -} - -struct SmallIntTypeConstraint { - min: i16, -} - -impl Constraint for SmallIntTypeConstraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError> { - match lexical::parse::(in_value) { - Ok(value) => { - if self.min <= value { - Ok(()) - } else { - Err(ConstraintError::OutOfRange) - } - } - Err(e) if e.code == lexical::ErrorCode::InvalidDigit => { - Err(ConstraintError::TypeMismatch(in_value.to_owned())) - } - Err(_) => Err(ConstraintError::OutOfRange), - } - } -} - -struct SmallIntTypeSerializer; - -impl Serializer for SmallIntTypeSerializer { - #[allow(clippy::match_wild_err_arm)] - fn ser(&self, in_value: &str) -> Vec { - match lexical::parse::(in_value) { - Ok(parsed) => parsed.to_be_bytes().to_vec(), - Err(_) => unreachable!(), - } - } - - fn des(&self, out_value: &[u8]) -> String { - i16::from_be_bytes(out_value[0..2].try_into().unwrap()).to_string() - } -} - -struct IntegerSqlTypeConstraint { - min: i32, -} - -impl Constraint for IntegerSqlTypeConstraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError> { - match lexical::parse::(in_value) { - Ok(value) => { - if self.min <= value { - Ok(()) - } else { - Err(ConstraintError::OutOfRange) - } - } - Err(e) if e.code == lexical::ErrorCode::InvalidDigit => { - Err(ConstraintError::TypeMismatch(in_value.to_owned())) - } - Err(_) => Err(ConstraintError::OutOfRange), - } - } -} - -struct IntegerSqlTypeSerializer; - -impl Serializer for IntegerSqlTypeSerializer { - #[allow(clippy::match_wild_err_arm)] - fn ser(&self, in_value: &str) -> Vec { - match lexical::parse::(in_value) { - Ok(parsed) => parsed.to_be_bytes().to_vec(), - Err(_) => unreachable!(), - } - } - - fn des(&self, out_value: &[u8]) -> String { - i32::from_be_bytes(out_value[0..4].try_into().unwrap()).to_string() - } -} - -struct BigIntTypeConstraint { - min: i64, -} - -impl Constraint for BigIntTypeConstraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError> { - match lexical::parse::(in_value) { - Ok(value) => { - if self.min <= value { - Ok(()) - } else { - Err(ConstraintError::OutOfRange) - } - } - Err(e) if e.code == lexical::ErrorCode::InvalidDigit => { - Err(ConstraintError::TypeMismatch(in_value.to_owned())) - } - Err(_) => Err(ConstraintError::OutOfRange), - } - } -} - -struct BigIntTypeSerializer; - -impl Serializer for BigIntTypeSerializer { - #[allow(clippy::match_wild_err_arm)] - fn ser(&self, in_value: &str) -> Vec { - match lexical::parse::(in_value) { - Ok(parsed) => parsed.to_be_bytes().to_vec(), - Err(_) => unreachable!(), - } - } - - fn des(&self, out_value: &[u8]) -> String { - i64::from_be_bytes(out_value[0..8].try_into().unwrap()).to_string() - } -} - -struct CharSqlTypeConstraint { - length: u64, -} - -impl Constraint for CharSqlTypeConstraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError> { - let trimmed = in_value.trim_end(); - if trimmed.len() > self.length as usize { - Err(ConstraintError::ValueTooLong(self.length)) - } else { - Ok(()) - } - } -} - -struct CharSqlTypeSerializer; - -impl Serializer for CharSqlTypeSerializer { - fn ser(&self, in_value: &str) -> Vec { - in_value.trim_end().as_bytes().to_vec() - } - - fn des(&self, out_value: &[u8]) -> String { - String::from_utf8(out_value.to_vec()).unwrap() - } -} - -struct VarCharSqlTypeConstraint { - length: u64, -} - -impl Constraint for VarCharSqlTypeConstraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError> { - let trimmed = in_value.trim_end(); - if trimmed.len() > self.length as usize { - Err(ConstraintError::ValueTooLong(self.length)) - } else { - Ok(()) - } - } -} - -struct VarCharSqlTypeSerializer; - -impl Serializer for VarCharSqlTypeSerializer { - fn ser(&self, in_value: &str) -> Vec { - in_value.trim_end().as_bytes().to_vec() - } - - fn des(&self, out_value: &[u8]) -> String { - String::from_utf8(out_value.to_vec()).unwrap() - } -} - -struct BoolSqlTypeConstraint; - -impl Constraint for BoolSqlTypeConstraint { - fn validate(&self, in_value: &str) -> Result<(), ConstraintError> { - let normalized_value = in_value.to_lowercase(); - match normalized_value.as_str() { - "true" | "false" | "t" | "f" => Ok(()), - "yes" | "no" | "y" | "n" => Ok(()), - "on" | "off" => Ok(()), - "1" | "0" => Ok(()), - _ => Err(ConstraintError::TypeMismatch(in_value.to_owned())), - } - } -} - -struct BoolSqlTypeSerializer; - -impl Serializer for BoolSqlTypeSerializer { - fn ser(&self, in_value: &str) -> Vec { - let normalized_value = in_value.to_lowercase(); - match normalized_value.as_str() { - "true" | "t" | "yes" | "y" | "on" | "1" => vec![1u8], - _ => vec![0u8], - } - } - - fn des(&self, out_value: &[u8]) -> String { - // The datatype output function for type boolean always emits either - // t or f, as shown in Example 8.2. - // See https://www.postgresql.org/docs/12/datatype-boolean.html#DATATYPE-BOOLEAN-EXAMPLE - match out_value { - [0u8] => "f".to_string(), - [1u8] => "t".to_string(), - other => panic!("Expected byte 0 or 1, but got {:?}", other), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -419,424 +153,4 @@ mod tests { assert_eq!(pg_type, PostgreSqlType::DoublePrecision); } } - - #[cfg(test)] - mod ints { - use super::*; - - #[cfg(test)] - mod small { - use super::*; - - #[cfg(test)] - mod serialization { - use super::*; - - #[rstest::fixture] - fn serializer() -> Box { - SqlType::SmallInt(i16::min_value()).serializer() - } - - #[rstest::rstest] - fn serialize(serializer: Box) { - assert_eq!(serializer.ser("1"), vec![0, 1]) - } - - #[rstest::rstest] - fn deserialize(serializer: Box) { - assert_eq!(serializer.des(&[0, 1]), "1".to_owned()) - } - } - - #[cfg(test)] - mod validation { - use super::*; - - #[rstest::fixture] - fn constraint() -> Box { - SqlType::SmallInt(i16::min_value()).constraint() - } - - #[rstest::rstest] - fn in_range(constraint: Box) { - assert_eq!(constraint.validate("1"), Ok(())); - assert_eq!(constraint.validate("32767"), Ok(())); - assert_eq!(constraint.validate("-32768"), Ok(())); - } - - #[rstest::rstest] - fn greater_than_max(constraint: Box) { - assert_eq!(constraint.validate("32769"), Err(ConstraintError::OutOfRange)) - } - - #[rstest::rstest] - fn less_than_min(constraint: Box) { - assert_eq!(constraint.validate("-32769"), Err(ConstraintError::OutOfRange)) - } - - #[rstest::rstest] - fn a_float_number(constraint: Box) { - assert_eq!( - constraint.validate("-3276.9"), - Err(ConstraintError::TypeMismatch("-3276.9".to_owned())) - ) - } - - #[rstest::rstest] - fn a_string(constraint: Box) { - assert_eq!( - constraint.validate("str"), - Err(ConstraintError::TypeMismatch("str".to_owned())) - ) - } - - #[test] - fn min_bound() { - let constraint = SqlType::SmallInt(0).constraint(); - - assert_eq!(constraint.validate("-1"), Err(ConstraintError::OutOfRange)) - } - } - } - - #[cfg(test)] - mod integer { - use super::*; - - #[cfg(test)] - mod serialization { - use super::*; - - #[rstest::fixture] - fn serializer() -> Box { - SqlType::Integer(i32::min_value()).serializer() - } - - #[rstest::rstest] - fn serialize(serializer: Box) { - assert_eq!(serializer.ser("1"), vec![0, 0, 0, 1]) - } - - #[rstest::rstest] - fn deserialize(serializer: Box) { - assert_eq!(serializer.des(&[0, 0, 0, 1]), "1".to_owned()) - } - } - - #[cfg(test)] - mod validation { - use super::*; - - #[rstest::fixture] - fn constraint() -> Box { - SqlType::Integer(i32::min_value()).constraint() - } - - #[rstest::rstest] - fn in_range(constraint: Box) { - assert_eq!(constraint.validate("1"), Ok(())); - assert_eq!(constraint.validate("-2147483648"), Ok(())); - assert_eq!(constraint.validate("2147483647"), Ok(())); - } - - #[rstest::rstest] - fn greater_than_max(constraint: Box) { - assert_eq!(constraint.validate("2147483649"), Err(ConstraintError::OutOfRange)) - } - - #[rstest::rstest] - fn less_than_min(constraint: Box) { - assert_eq!(constraint.validate("-2147483649"), Err(ConstraintError::OutOfRange)) - } - - #[rstest::rstest] - fn a_float_number(constraint: Box) { - assert_eq!( - constraint.validate("-214748.3649"), - Err(ConstraintError::TypeMismatch("-214748.3649".to_owned())) - ) - } - - #[rstest::rstest] - fn a_string(constraint: Box) { - assert_eq!( - constraint.validate("str"), - Err(ConstraintError::TypeMismatch("str".to_owned())) - ) - } - - #[test] - fn min_bound() { - let constraint = SqlType::Integer(0).constraint(); - - assert_eq!(constraint.validate("-1"), Err(ConstraintError::OutOfRange)) - } - } - } - - #[cfg(test)] - mod big_int { - use super::*; - - #[cfg(test)] - mod serialization { - use super::*; - - #[rstest::fixture] - fn serializer() -> Box { - SqlType::BigInt(i64::min_value()).serializer() - } - - #[rstest::rstest] - fn serialize(serializer: Box) { - assert_eq!(serializer.ser("1"), vec![0, 0, 0, 0, 0, 0, 0, 1]) - } - - #[rstest::rstest] - fn deserialize(serializer: Box) { - assert_eq!(serializer.des(&[0, 0, 0, 0, 0, 0, 0, 1]), "1".to_owned()) - } - } - - #[cfg(test)] - mod validation { - use super::*; - - #[rstest::fixture] - fn constraint() -> Box { - SqlType::BigInt(i64::min_value()).constraint() - } - - #[rstest::rstest] - fn in_range(constraint: Box) { - assert_eq!(constraint.validate("1"), Ok(())); - assert_eq!(constraint.validate("-9223372036854775808"), Ok(())); - assert_eq!(constraint.validate("9223372036854775807"), Ok(())); - } - - #[rstest::rstest] - fn greater_than_max(constraint: Box) { - assert_eq!( - constraint.validate("9223372036854775809"), - Err(ConstraintError::OutOfRange) - ) - } - - #[rstest::rstest] - fn less_than_min(constraint: Box) { - assert_eq!( - constraint.validate("-9223372036854775809"), - Err(ConstraintError::OutOfRange) - ) - } - - #[rstest::rstest] - fn a_float_number(constraint: Box) { - assert_eq!( - constraint.validate("-3276.9"), - Err(ConstraintError::TypeMismatch("-3276.9".to_owned())) - ) - } - - #[rstest::rstest] - fn a_string(constraint: Box) { - assert_eq!( - constraint.validate("str"), - Err(ConstraintError::TypeMismatch("str".to_owned())) - ) - } - - #[test] - fn min_bound() { - let constraint = SqlType::BigInt(0).constraint(); - - assert_eq!(constraint.validate("-1"), Err(ConstraintError::OutOfRange)) - } - } - } - } - - #[cfg(test)] - mod strings { - use super::*; - - #[cfg(test)] - mod chars { - use super::*; - - #[cfg(test)] - mod serialization { - use super::*; - - #[rstest::fixture] - fn serializer() -> Box { - SqlType::Char(10).serializer() - } - - #[rstest::rstest] - fn serialize(serializer: Box) { - assert_eq!(serializer.ser("str"), vec![115, 116, 114]) - } - - #[rstest::rstest] - fn deserialize(serializer: Box) { - assert_eq!(serializer.des(&[115, 116, 114]), "str".to_owned()) - } - } - - #[cfg(test)] - mod validation { - use super::*; - - #[rstest::fixture] - fn constraint() -> Box { - SqlType::Char(10).constraint() - } - - #[rstest::rstest] - fn in_length(constraint: Box) { - assert_eq!(constraint.validate("1"), Ok(())) - } - - #[rstest::rstest] - fn too_long(constraint: Box) { - assert_eq!( - constraint.validate("1".repeat(20).as_str()), - Err(ConstraintError::ValueTooLong(10)) - ) - } - } - } - - #[cfg(test)] - mod var_chars { - use super::*; - - #[cfg(test)] - mod serialization { - use super::*; - - #[rstest::fixture] - fn serializer() -> Box { - SqlType::VarChar(10).serializer() - } - - #[rstest::rstest] - fn serialize(serializer: Box) { - assert_eq!(serializer.ser("str"), vec![115, 116, 114]) - } - - #[rstest::rstest] - fn deserialize(serializer: Box) { - assert_eq!(serializer.des(&[115, 116, 114]), "str".to_owned()) - } - } - - #[cfg(test)] - mod validation { - use super::*; - - #[rstest::fixture] - fn constraint() -> Box { - SqlType::VarChar(10).constraint() - } - - #[rstest::rstest] - fn in_length(constraint: Box) { - assert_eq!(constraint.validate("1"), Ok(())) - } - - #[rstest::rstest] - fn too_long(constraint: Box) { - assert_eq!( - constraint.validate("1".repeat(20).as_str()), - Err(ConstraintError::ValueTooLong(10)) - ) - } - } - } - } - - mod bool { - use super::*; - - #[cfg(test)] - mod serialization { - use super::*; - - #[rstest::fixture] - fn serializer() -> Box { - SqlType::Bool.serializer() - } - - #[rstest::rstest] - fn serialize(serializer: Box) { - assert_eq!(serializer.ser("TRUE"), vec![1]); - assert_eq!(serializer.ser("true"), vec![1]); - assert_eq!(serializer.ser("t"), vec![1]); - assert_eq!(serializer.ser("yes"), vec![1]); - assert_eq!(serializer.ser("y"), vec![1]); - assert_eq!(serializer.ser("on"), vec![1]); - assert_eq!(serializer.ser("1"), vec![1]); - assert_eq!(serializer.ser("YES"), vec![1]); - - assert_eq!(serializer.ser("FALSE"), vec![0]); - assert_eq!(serializer.ser("false"), vec![0]); - assert_eq!(serializer.ser("f"), vec![0]); - assert_eq!(serializer.ser("no"), vec![0]); - assert_eq!(serializer.ser("n"), vec![0]); - assert_eq!(serializer.ser("off"), vec![0]); - assert_eq!(serializer.ser("0"), vec![0]); - assert_eq!(serializer.ser("NO"), vec![0]); - } - - #[rstest::rstest] - fn deserialize(serializer: Box) { - assert_eq!(serializer.des(&[0]), "f".to_owned()); - assert_eq!(serializer.des(&[1]), "t".to_owned()); - } - } - - #[cfg(test)] - mod validation { - use super::*; - - #[rstest::fixture] - fn constraint() -> Box { - SqlType::Bool.constraint() - } - - #[rstest::rstest] - fn is_ok_true(constraint: Box) { - assert_eq!(constraint.validate("TRUE"), Ok(())); - assert_eq!(constraint.validate("true"), Ok(())); - assert_eq!(constraint.validate("t"), Ok(())); - assert_eq!(constraint.validate("yes"), Ok(())); - assert_eq!(constraint.validate("y"), Ok(())); - assert_eq!(constraint.validate("on"), Ok(())); - assert_eq!(constraint.validate("1"), Ok(())); - assert_eq!(constraint.validate("YES"), Ok(())); - } - - #[rstest::rstest] - fn is_ok_false(constraint: Box) { - assert_eq!(constraint.validate("FALSE"), Ok(())); - assert_eq!(constraint.validate("false"), Ok(())); - assert_eq!(constraint.validate("f"), Ok(())); - assert_eq!(constraint.validate("no"), Ok(())); - assert_eq!(constraint.validate("n"), Ok(())); - assert_eq!(constraint.validate("off"), Ok(())); - assert_eq!(constraint.validate("0"), Ok(())); - assert_eq!(constraint.validate("NO"), Ok(())); - } - - #[rstest::rstest] - fn is_non_bool(constraint: Box) { - assert_eq!( - constraint.validate("oops"), - Err(ConstraintError::TypeMismatch("oops".to_owned())) - ) - } - } - } }