From 0411a1f23cfa0c89f7d9010d4753ff6527317fd9 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 14:30:36 +0900 Subject: [PATCH 01/62] feat: add new unsigned integer data type --- core/src/data/value/binary_op/integer/u64.rs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 core/src/data/value/binary_op/integer/u64.rs diff --git a/core/src/data/value/binary_op/integer/u64.rs b/core/src/data/value/binary_op/integer/u64.rs new file mode 100644 index 000000000..8acfbf79d --- /dev/null +++ b/core/src/data/value/binary_op/integer/u64.rs @@ -0,0 +1,9 @@ +use {crate::prelude::Value, std::cmp::Ordering}; + +super::macros::impl_try_binary_op!(U64, u64); +#[cfg(test)] +super::macros::generate_binary_op_tests!(U64, u64); + +super::macros::impl_partial_cmp_ord_method!(u64); +#[cfg(test)] +super::macros::generate_cmp_ord_tests!(u64); From 45e8159cdf983b6a2b29d45acd87073f35549585 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 14:30:40 +0900 Subject: [PATCH 02/62] feat: add new unsinged integer data type --- core/src/data/value/binary_op/integer/mod.rs | 3 +++ core/src/data/value/binary_op/integer/u128.rs | 9 +++++++++ core/src/data/value/binary_op/integer/u32.rs | 9 +++++++++ 3 files changed, 21 insertions(+) create mode 100644 core/src/data/value/binary_op/integer/u128.rs create mode 100644 core/src/data/value/binary_op/integer/u32.rs diff --git a/core/src/data/value/binary_op/integer/mod.rs b/core/src/data/value/binary_op/integer/mod.rs index d35c84a4b..5d921fa59 100644 --- a/core/src/data/value/binary_op/integer/mod.rs +++ b/core/src/data/value/binary_op/integer/mod.rs @@ -5,5 +5,8 @@ mod i64; mod i8; mod u16; mod u8; +mod u32; +mod u64; +mod u128; mod macros; diff --git a/core/src/data/value/binary_op/integer/u128.rs b/core/src/data/value/binary_op/integer/u128.rs new file mode 100644 index 000000000..f534eca3f --- /dev/null +++ b/core/src/data/value/binary_op/integer/u128.rs @@ -0,0 +1,9 @@ +use {crate::prelude::Value, std::cmp::Ordering}; + +super::macros::impl_try_binary_op!(U128, u128); +#[cfg(test)] +super::macros::generate_binary_op_tests!(U128, u128); + +super::macros::impl_partial_cmp_ord_method!(u128); +#[cfg(test)] +super::macros::generate_cmp_ord_tests!(u128); diff --git a/core/src/data/value/binary_op/integer/u32.rs b/core/src/data/value/binary_op/integer/u32.rs new file mode 100644 index 000000000..8ae4480be --- /dev/null +++ b/core/src/data/value/binary_op/integer/u32.rs @@ -0,0 +1,9 @@ +use {crate::prelude::Value, std::cmp::Ordering}; + +super::macros::impl_try_binary_op!(U32, u32); +#[cfg(test)] +super::macros::generate_binary_op_tests!(U32, u32); + +super::macros::impl_partial_cmp_ord_method!(u32); +#[cfg(test)] +super::macros::generate_cmp_ord_tests!(u32); From 784cdcc6ed438ba0742dfe9f51cd92ac70b4709a Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 14:40:10 +0900 Subject: [PATCH 03/62] feat: add new datatype into macro.rs --- .../data/value/binary_op/integer/macros.rs | 30 +++++++++++++++++++ core/src/data/value/mod.rs | 3 ++ 2 files changed, 33 insertions(+) diff --git a/core/src/data/value/binary_op/integer/macros.rs b/core/src/data/value/binary_op/integer/macros.rs index 2277f7f60..7d7734e3b 100644 --- a/core/src/data/value/binary_op/integer/macros.rs +++ b/core/src/data/value/binary_op/integer/macros.rs @@ -85,6 +85,36 @@ macro_rules! impl_method { } .into() }), + U32(rhs) => $lhs + .$method($lhs_primitive::try_from($rhs)?) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: $lhs_variant($lhs), + rhs: U32(rhs), + operator: $op, + } + .into() + }), + U64(rhs) => $lhs + .$method($lhs_primitive::try_from($rhs)?) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: $lhs_variant($lhs), + rhs: U64(rhs), + operator: $op, + } + .into() + }), + U128(rhs) => $lhs + .$method($lhs_primitive::try_from($rhs)?) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: $lhs_variant($lhs), + rhs: U128(rhs), + operator: $op, + } + .into() + }), F64(rhs) => $lhs .$method($lhs_primitive::try_from($rhs)?) .ok_or_else(|| { diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index f8462d243..75ed26963 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -34,6 +34,9 @@ pub enum Value { I128(i128), U8(u8), U16(u16), + U32(u32), + U64(u64), + U128(u128), F64(f64), Decimal(Decimal), Str(String), From f22f06c9fd088f05fd74b85b6265b0755d520ed9 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 16:21:11 +0900 Subject: [PATCH 04/62] feat: implememnt convert for unsigned int --- core/src/ast/data_type.rs | 3 + core/src/data/bigdecimal_ext.rs | 15 ++ core/src/data/value/binary_op/decimal.rs | 57 +++++++ core/src/data/value/convert.rs | 187 +++++++++++++++++++++-- core/src/data/value/expr.rs | 10 +- core/src/data/value/mod.rs | 76 ++++++++- 6 files changed, 336 insertions(+), 12 deletions(-) diff --git a/core/src/ast/data_type.rs b/core/src/ast/data_type.rs index dd14ee133..235e4971f 100644 --- a/core/src/ast/data_type.rs +++ b/core/src/ast/data_type.rs @@ -14,6 +14,9 @@ pub enum DataType { Int128, Uint8, Uint16, + Uint32, + Uint64, + Uint128, Float, Text, Bytea, diff --git a/core/src/data/bigdecimal_ext.rs b/core/src/data/bigdecimal_ext.rs index a6ff78d15..a92edc96b 100644 --- a/core/src/data/bigdecimal_ext.rs +++ b/core/src/data/bigdecimal_ext.rs @@ -8,6 +8,9 @@ pub trait BigDecimalExt { fn to_i128(&self) -> Option; fn to_u8(&self) -> Option; fn to_u16(&self) -> Option; + fn to_u32(&self) -> Option; + fn to_u64(&self) -> Option; + fn to_u128(&self) -> Option; fn to_f64(&self) -> Option; } @@ -44,6 +47,18 @@ impl BigDecimalExt for BigDecimal { self.is_integer() .then(|| bigdecimal::ToPrimitive::to_u16(self))? } + fn to_u32(&self) -> Option { + self.is_integer() + .then(|| bigdecimal::ToPrimitive::to_u32(self))? + } + fn to_u64(&self) -> Option { + self.is_integer() + .then(|| bigdecimal::ToPrimitive::to_u64(self))? + } + fn to_u128(&self) -> Option { + self.is_integer() + .then(|| bigdecimal::ToPrimitive::to_u128(self))? + } fn to_f64(&self) -> Option { bigdecimal::ToPrimitive::to_f64(self) } diff --git a/core/src/data/value/binary_op/decimal.rs b/core/src/data/value/binary_op/decimal.rs index dda2ff22a..5a26b7dd2 100644 --- a/core/src/data/value/binary_op/decimal.rs +++ b/core/src/data/value/binary_op/decimal.rs @@ -19,6 +19,7 @@ impl PartialEq for Decimal { I128(other) => *self == Decimal::from(*other), U8(other) => *self == Decimal::from(*other), U16(other) => *self == Decimal::from(*other), + U32(other) => *self == Decimal::from(*other), F64(other) => Decimal::from_f64_retain(*other) .map(|x| *self == x) .unwrap_or(false), @@ -37,6 +38,7 @@ impl PartialOrd for Decimal { I128(rhs) => self.partial_cmp(&(Decimal::from(rhs))), U8(rhs) => self.partial_cmp(&(Decimal::from(rhs))), U16(rhs) => self.partial_cmp(&(Decimal::from(rhs))), + U32(rhs) => self.partial_cmp(&(Decimal::from(rhs))), F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| self.partial_cmp(&x)) .unwrap_or(None), @@ -119,6 +121,17 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U32(rhs) => lhs + .checked_add(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U32(rhs), + operator: NumericBinaryOperator::Add, + } + .into() + }) + .map(Decimal), F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs + x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -213,6 +226,17 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U32(rhs) => lhs + .checked_sub(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U32(rhs), + operator: NumericBinaryOperator::Subtract, + } + .into() + }) + .map(Decimal), F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs - x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -307,6 +331,17 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U32(rhs) => lhs + .checked_mul(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U32(rhs), + operator: NumericBinaryOperator::Multiply, + } + .into() + }) + .map(Decimal), F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs * x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -401,6 +436,17 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U32(rhs) => lhs + .checked_div(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U32(rhs), + operator: NumericBinaryOperator::Divide, + } + .into() + }) + .map(Decimal), F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs / x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -495,6 +541,17 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U32(rhs) => lhs + .checked_rem(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U32(rhs), + operator: NumericBinaryOperator::Modulo, + } + .into() + }) + .map(Decimal), F64(rhs) => match Decimal::from_f64_retain(rhs) { Some(x) => lhs .checked_rem(x) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index e7b5b062f..e9373543f 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -25,6 +25,9 @@ impl From<&Value> for String { Value::I128(value) => value.to_string(), Value::U8(value) => value.to_string(), Value::U16(value) => value.to_string(), + Value::U32(value) => value.to_string(), + Value::U64(value) => value.to_string(), + Value::U128(value) => value.to_string(), Value::F64(value) => value.to_string(), Value::Date(value) => value.to_string(), Value::Timestamp(value) => value.to_string(), @@ -89,6 +92,21 @@ impl TryFrom<&Value> for bool { 0 => false, _ => return Err(ValueError::ImpossibleCast.into()), }, + Value::U32(value) => match value { + 1 => true, + 0 => false, + _ => return Err(ValueError::ImpossibleCast.into()), + }, + Value::U64(value) => match value { + 1 => true, + 0 => false, + _ => return Err(ValueError::ImpossibleCast.into()), + }, + Value::U128(value) => match value { + 1 => true, + 0 => false, + _ => return Err(ValueError::ImpossibleCast.into()), + }, Value::F64(value) => { if value.eq(&1.0) { true @@ -144,6 +162,9 @@ impl TryFrom<&Value> for i8 { Value::I128(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_i8().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -181,6 +202,9 @@ impl TryFrom<&Value> for i16 { Value::I128(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_i16().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -218,6 +242,9 @@ impl TryFrom<&Value> for i32 { Value::I128(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_i32().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -255,6 +282,9 @@ impl TryFrom<&Value> for i64 { Value::I128(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_i64().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -292,6 +322,9 @@ impl TryFrom<&Value> for i128 { Value::I128(value) => *value, Value::U8(value) => *value as i128, Value::U16(value) => *value as i128, + Value::U32(value) => value.to_i128().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_i128().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_i128().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_i128().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -329,6 +362,9 @@ impl TryFrom<&Value> for u8 { Value::I128(value) => value.to_u8().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => *value, Value::U16(value) => value.to_u8().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_u8().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_u8().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_u8().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_u8().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -365,6 +401,9 @@ impl TryFrom<&Value> for u16 { Value::I128(value) => value.to_u16().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => value.to_u16().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => *value, + Value::U32(value) => value.to_u16().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_u16().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_u16().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_u16().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -383,6 +422,127 @@ impl TryFrom<&Value> for u16 { } } +impl TryFrom<&Value> for u32 { + type Error = Error; + + fn try_from(v: &Value) -> Result { + Ok(match v { + Value::Bool(value) => { + if *value { + 1 + } else { + 0 + } + } + Value::I8(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::I16(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::I32(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::I64(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::I128(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::U8(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::U16(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => *value, + Value::U64(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::F64(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::Str(value) => value + .parse::() + .map_err(|_| ValueError::ImpossibleCast)?, + Value::Decimal(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::Date(_) + | Value::Timestamp(_) + | Value::Time(_) + | Value::Interval(_) + | Value::Uuid(_) + | Value::Map(_) + | Value::List(_) + | Value::Bytea(_) + | Value::Null => return Err(ValueError::ImpossibleCast.into()), + }) + } +} + +impl TryFrom<&Value> for u64 { + type Error = Error; + + fn try_from(v: &Value) -> Result { + Ok(match v { + Value::Bool(value) => { + if *value { + 1 + } else { + 0 + } + } + Value::I8(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::I16(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::I32(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::I64(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::I128(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::U8(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::U16(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => *value, + Value::U128(value) =>value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::F64(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::Str(value) => value + .parse::() + .map_err(|_| ValueError::ImpossibleCast)?, + Value::Decimal(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::Date(_) + | Value::Timestamp(_) + | Value::Time(_) + | Value::Interval(_) + | Value::Uuid(_) + | Value::Map(_) + | Value::List(_) + | Value::Bytea(_) + | Value::Null => return Err(ValueError::ImpossibleCast.into()), + }) + } +} + +impl TryFrom<&Value> for u128 { + type Error = Error; + + fn try_from(v: &Value) -> Result { + Ok(match v { + Value::Bool(value) => { + if *value { + 1 + } else { + 0 + } + } + Value::I8(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::I16(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::I32(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::I64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::I128(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::U8(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::U16(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) =>*value, + Value::F64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::Str(value) => value + .parse::() + .map_err(|_| ValueError::ImpossibleCast)?, + Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::Uuid(value) => *value, + Value::Date(_) + | Value::Timestamp(_) + | Value::Time(_) + | Value::Interval(_) + | Value::Uuid(_) + | Value::Map(_) + | Value::List(_) + | Value::Bytea(_) + | Value::Null => return Err(ValueError::ImpossibleCast.into()), + }) + } +} + impl TryFrom<&Value> for f64 { type Error = Error; @@ -402,6 +562,9 @@ impl TryFrom<&Value> for f64 { Value::I128(value) => *value as f64, Value::U8(value) => value.to_f64().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => value.to_f64().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_f64().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_f64().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_f64().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => *value, Value::Str(value) => value .parse::() @@ -439,6 +602,9 @@ impl TryFrom<&Value> for usize { Value::I128(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_usize().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -476,6 +642,9 @@ impl TryFrom<&Value> for Decimal { Value::I128(value) => Decimal::from_i128(*value).ok_or(ValueError::ImpossibleCast)?, Value::U8(value) => Decimal::from_u8(*value).ok_or(ValueError::ImpossibleCast)?, Value::U16(value) => Decimal::from_u16(*value).ok_or(ValueError::ImpossibleCast)?, + Value::U32(value) => Decimal::from_u32(*value).ok_or(ValueError::ImpossibleCast)?, + Value::U64(value) => Decimal::from_u64(*value).ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => Decimal::from_u128(*value).ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => Decimal::from_f64(*value).ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => { Decimal::from_str(value).map_err(|_| ValueError::ImpossibleCast)? @@ -560,16 +729,16 @@ impl TryFrom<&Value> for Interval { } } -impl TryFrom<&Value> for u128 { - type Error = Error; +// impl TryFrom<&Value> for u128 { +// type Error = Error; - fn try_from(v: &Value) -> Result { - match v { - Value::Uuid(value) => Ok(*value), - _ => Err(ValueError::ImpossibleCast.into()), - } - } -} +// fn try_from(v: &Value) -> Result { +// match v { +// Value::Uuid(value) => Ok(*value), +// _ => Err(ValueError::ImpossibleCast.into()), +// } +// } +// } #[cfg(test)] mod tests { diff --git a/core/src/data/value/expr.rs b/core/src/data/value/expr.rs index 999f29251..696b27cb8 100644 --- a/core/src/data/value/expr.rs +++ b/core/src/data/value/expr.rs @@ -41,7 +41,15 @@ impl TryFrom for Expr { Value::U16(v) => Expr::Literal(AstLiteral::Number( BigDecimal::from_u16(v).ok_or(ValueToExprConversionFailure)?, )), - + Value::U32(v) => Expr::Literal(AstLiteral::Number( + BigDecimal::from_u32(v).ok_or(ValueToExprConversionFailure)?, + )), + Value::U64(v) => Expr::Literal(AstLiteral::Number( + BigDecimal::from_u64(v).ok_or(ValueToExprConversionFailure)?, + )), + Value::U128(v) => Expr::Literal(AstLiteral::Number( + BigDecimal::from_u128(v).ok_or(ValueToExprConversionFailure)?, + )), Value::F64(v) => Expr::Literal(AstLiteral::Number( BigDecimal::from_f64(v).ok_or(ValueToExprConversionFailure)?, )), diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 75ed26963..0e6ddb32b 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -61,6 +61,9 @@ impl PartialEq for Value { (Value::I128(l), _) => l == other, (Value::U8(l), _) => l == other, (Value::U16(l), _) => l == other, + (Value::U32(l), _) => l == other, + (Value::U64(l), _) => l == other, + (Value::U128(l), _) => l == other, (Value::F64(l), _) => l == other, (Value::Decimal(l), Value::Decimal(r)) => l == r, (Value::Bool(l), Value::Bool(r)) => l == r, @@ -96,6 +99,9 @@ impl PartialOrd for Value { (Value::I128(l), _) => l.partial_cmp(other), (Value::U8(l), _) => l.partial_cmp(other), (Value::U16(l), _) => l.partial_cmp(other), + (Value::U32(l), _) => l.partial_cmp(other), + (Value::U64(l), _) => l.partial_cmp(other), + (Value::U128(l), _) => l.partial_cmp(other), (Value::F64(l), _) => l.partial_cmp(other), (Value::Decimal(l), Value::Decimal(r)) => Some(l.cmp(r)), (Value::Bool(l), Value::Bool(r)) => Some(l.cmp(r)), @@ -127,6 +133,9 @@ impl Value { Value::I128(v) => *v == 0, Value::U8(v) => *v == 0, Value::U16(v) => *v == 0, + Value::U32(v) => *v == 0, + Value::U64(v) => *v == 0, + Value::U128(v) => *v == 0, Value::F64(v) => *v == 0.0, Value::Decimal(v) => *v == Decimal::ZERO, _ => false, @@ -142,6 +151,9 @@ impl Value { Value::I128(_) => Some(DataType::Int128), Value::U8(_) => Some(DataType::Uint8), Value::U16(_) => Some(DataType::Uint16), + Value::U32(_) => Some(DataType::Uint32), + Value::U64(_) => Some(DataType::Uint64), + Value::U128(_) => Some(DataType::Uint128), Value::F64(_) => Some(DataType::Float), Value::Decimal(_) => Some(DataType::Decimal), Value::Bool(_) => Some(DataType::Boolean), @@ -167,6 +179,9 @@ impl Value { Value::I128(_) => matches!(data_type, DataType::Int128), Value::U8(_) => matches!(data_type, DataType::Uint8), Value::U16(_) => matches!(data_type, DataType::Uint16), + Value::U32(_) => matches!(data_type, DataType::Uint32), + Value::U64(_) => matches!(data_type, DataType::Uint64), + Value::U128(_) => matches!(data_type, DataType::Uint128), Value::F64(_) => matches!(data_type, DataType::Float), Value::Decimal(_) => matches!(data_type, DataType::Decimal), Value::Bool(_) => matches!(data_type, DataType::Boolean), @@ -210,6 +225,9 @@ impl Value { | (DataType::Int128, Value::I128(_)) | (DataType::Uint8, Value::U8(_)) | (DataType::Uint16, Value::U16(_)) + | (DataType::Uint32, Value::U32(_)) + | (DataType::Uint64, Value::U64(_)) + | (DataType::Uint128, Value::U128(_)) | (DataType::Float, Value::F64(_)) | (DataType::Decimal, Value::Decimal(_)) | (DataType::Boolean, Value::Bool(_)) @@ -230,6 +248,9 @@ impl Value { (DataType::Int128, value) => value.try_into().map(Value::I128), (DataType::Uint8, value) => value.try_into().map(Value::U8), (DataType::Uint16, value) => value.try_into().map(Value::U16), + (DataType::Uint32, value) => value.try_into().map(Value::U32), + (DataType::Uint64, value) => value.try_into().map(Value::U64), + (DataType::Uint128, value) => value.try_into().map(Value::U128), (DataType::Float, value) => value.try_into().map(Value::F64), (DataType::Decimal, value) => value.try_into().map(Value::Decimal), (DataType::Text, value) => Ok(Value::Str(value.into())), @@ -261,6 +282,9 @@ impl Value { (I128(a), b) => a.try_add(b), (U8(a), b) => a.try_add(b), (U16(a), b) => a.try_add(b), + (U32(a), b) => a.try_add(b), + (U64(a), b) => a.try_add(b), + (U128(a), b) => a.try_add(b), (F64(a), b) => a.try_add(b), (Decimal(a), b) => a.try_add(b), (Date(a), Time(b)) => Ok(Timestamp(NaiveDateTime::new(*a, *b))), @@ -275,6 +299,9 @@ impl Value { | (Null, I128(_)) | (Null, U8(_)) | (Null, U16(_)) + | (Null, U32(_)) + | (Null, U64(_)) + | (Null, U128(_)) | (Null, F64(_)) | (Null, Decimal(_)) | (Null, Date(_)) @@ -305,6 +332,9 @@ impl Value { (I128(a), _) => a.try_subtract(other), (U8(a), _) => a.try_subtract(other), (U16(a), _) => a.try_subtract(other), + (U32(a), _) => a.try_subtract(other), + (U64(a), _) => a.try_subtract(other), + (U128(a), _) => a.try_subtract(other), (F64(a), _) => a.try_subtract(other), (Decimal(a), _) => a.try_subtract(other), (Date(a), Date(b)) => Ok(Interval(I::days((*a - *b).num_days() as i32))), @@ -333,6 +363,9 @@ impl Value { | (Null, I128(_)) | (Null, U8(_)) | (Null, U16(_)) + | (Null, U32(_)) + | (Null, U64(_)) + | (Null, U128(_)) | (Null, F64(_)) | (Null, Decimal(_)) | (Null, Date(_)) @@ -364,6 +397,9 @@ impl Value { (I128(a), _) => a.try_multiply(other), (U8(a), _) => a.try_multiply(other), (U16(a), _) => a.try_multiply(other), + (U32(a), _) => a.try_multiply(other), + (U64(a), _) => a.try_multiply(other), + (U128(a), _) => a.try_multiply(other), (F64(a), _) => a.try_multiply(other), (Decimal(a), _) => a.try_multiply(other), (Interval(a), I8(b)) => Ok(Interval(*a * *b)), @@ -379,6 +415,9 @@ impl Value { | (Null, I128(_)) | (Null, U8(_)) | (Null, U16(_)) + | (Null, U32(_)) + | (Null, U64(_)) + | (Null, U128(_)) | (Null, F64(_)) | (Null, Decimal(_)) | (Null, Interval(_)) @@ -408,6 +447,9 @@ impl Value { (I128(a), _) => a.try_divide(other), (U8(a), _) => a.try_divide(other), (U16(a), _) => a.try_divide(other), + (U32(a), _) => a.try_divide(other), + (U64(a), _) => a.try_divide(other), + (U128(a), _) => a.try_divide(other), (F64(a), _) => a.try_divide(other), (Decimal(a), _) => a.try_divide(other), (Interval(a), I8(b)) => Ok(Interval(*a / *b)), @@ -417,6 +459,9 @@ impl Value { (Interval(a), I128(b)) => Ok(Interval(*a / *b)), (Interval(a), U8(b)) => Ok(Interval(*a / *b)), (Interval(a), U16(b)) => Ok(Interval(*a / *b)), + (Interval(a), U32(b)) => Ok(Interval(*a / *b)), + (Interval(a), U64(b)) => Ok(Interval(*a / *b)), + (Interval(a), U128(b)) => Ok(Interval(*a / *b)), (Interval(a), F64(b)) => Ok(Interval(*a / *b)), (Null, I8(_)) | (Null, I16(_)) @@ -425,6 +470,9 @@ impl Value { | (Null, I128(_)) | (Null, U8(_)) | (Null, U16(_)) + | (Null, U32(_)) + | (Null, U64(_)) + | (Null, U128(_)) | (Null, F64(_)) | (Null, Decimal(_)) | (Interval(_), Null) @@ -453,6 +501,9 @@ impl Value { (I128(a), _) => a.try_modulo(other), (U8(a), _) => a.try_modulo(other), (U16(a), _) => a.try_modulo(other), + (U32(a), _) => a.try_modulo(other), + (U64(a), _) => a.try_modulo(other), + (U128(a), _) => a.try_modulo(other), (F64(a), _) => a.try_modulo(other), (Decimal(a), _) => a.try_modulo(other), (Null, I8(_)) @@ -462,6 +513,9 @@ impl Value { | (Null, I128(_)) | (Null, U8(_)) | (Null, U16(_)) + | (Null, U32(_)) + | (Null, U64(_)) + | (Null, U128(_)) | (Null, F64(_)) | (Null, Decimal(_)) | (Null, Null) => Ok(Null), @@ -482,7 +536,7 @@ impl Value { use Value::*; match self { - I8(_) | I16(_) | I32(_) | I64(_) | I128(_) | U8(_) | U16(_) | F64(_) | Interval(_) + I8(_) | I16(_) | I32(_) | I64(_) | I128(_) | U8(_) | U16(_) | U32(_)|U64(_)|U128(_)|F64(_) | Interval(_) | Decimal(_) => Ok(self.clone()), Null => Ok(Null), _ => Err(ValueError::UnaryPlusOnNonNumeric.into()), @@ -528,6 +582,9 @@ impl Value { I128(a) => factorial_function(*a).map(I128), U8(a) => factorial_function(*a as i128).map(I128), U16(a) => factorial_function(*a as i128).map(I128), + U32(a) => factorial_function(*a as i128).map(I128), + U64(a) => factorial_function(*a as i128).map(I128), + U128(a) => factorial_function(*a as i128).map(I128), F64(_) => Err(ValueError::FactorialOnNonInteger.into()), Null => Ok(Null), _ => Err(ValueError::FactorialOnNonNumeric.into()), @@ -578,7 +635,7 @@ impl Value { pub fn sqrt(&self) -> Result { use Value::*; match self { - I8(_) | I16(_) | I64(_) | I128(_) | U8(_) | U16(_) | F64(_) => { + I8(_) | I16(_) | I64(_) | I128(_) | U8(_) | U16(_) |U32(_)|U64(_)|U128(_)|F64(_) => { let a: f64 = self.try_into()?; Ok(Value::F64(a.sqrt())) } @@ -677,6 +734,9 @@ mod tests { assert_eq!(I128(1), I128(1)); assert_eq!(U8(1), U8(1)); assert_eq!(U16(1), U16(1)); + assert_eq!(U32(1), U32(1)); + assert_eq!(U64(1), U64(1)); + assert_eq!(U128(1), U128(1)); assert_eq!(I64(1), F64(1.0)); assert_eq!(F64(1.0), I64(1)); assert_eq!(F64(6.11), F64(6.11)); @@ -791,6 +851,18 @@ mod tests { assert_eq!(U16(1).partial_cmp(&U16(0)), Some(Ordering::Greater)); assert_eq!(U16(0).partial_cmp(&U16(0)), Some(Ordering::Equal)); assert_eq!(U16(0).partial_cmp(&U16(1)), Some(Ordering::Less)); + + assert_eq!(U32(1).partial_cmp(&U32(0)), Some(Ordering::Greater)); + assert_eq!(U32(0).partial_cmp(&U32(0)), Some(Ordering::Equal)); + assert_eq!(U32(0).partial_cmp(&U32(1)), Some(Ordering::Less)); + + assert_eq!(U64(1).partial_cmp(&U64(0)), Some(Ordering::Greater)); + assert_eq!(U64(0).partial_cmp(&U64(0)), Some(Ordering::Equal)); + assert_eq!(U64(0).partial_cmp(&U64(1)), Some(Ordering::Less)); + + assert_eq!(U128(1).partial_cmp(&U128(0)), Some(Ordering::Greater)); + assert_eq!(U128(0).partial_cmp(&U128(0)), Some(Ordering::Equal)); + assert_eq!(U128(0).partial_cmp(&U128(1)), Some(Ordering::Less)); } #[test] From bbfcede58e8ec51c35e90c01ddea11a9f4cb350c Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 16:37:07 +0900 Subject: [PATCH 05/62] feat: add unsinged int to primitive --- core/src/data/interval/primitive.rs | 125 +++++++++++++++++++++++++++ core/src/data/value/binary_op/f64.rs | 21 +++++ 2 files changed, 146 insertions(+) diff --git a/core/src/data/interval/primitive.rs b/core/src/data/interval/primitive.rs index 84cd28ad6..0cc097ffb 100644 --- a/core/src/data/interval/primitive.rs +++ b/core/src/data/interval/primitive.rs @@ -79,6 +79,40 @@ impl Mul for Interval { } } } + +impl Mul for Interval { + type Output = Self; + + fn mul(self, rhs: u32) -> Self { + match self { + Interval::Month(v) => Interval::Month(((v as u32) * rhs) as i32), + Interval::Microsecond(v) => Interval::Microsecond(((v as u32) * rhs) as i64), + } + } +} + +impl Mul for Interval { + type Output = Self; + + fn mul(self, rhs: u64) -> Self { + match self { + Interval::Month(v) => Interval::Month(((v as u64) * rhs) as i32), + Interval::Microsecond(v) => Interval::Microsecond(((v as u64) * rhs) as i64), + } + } +} + +impl Mul for Interval { + type Output = Self; + + fn mul(self, rhs: u128) -> Self { + match self { + Interval::Month(v) => Interval::Month(((v as u128) * rhs) as i32), + Interval::Microsecond(v) => Interval::Microsecond(((v as u128) * rhs) as i64), + } + } +} + impl Mul for Interval { type Output = Self; @@ -146,6 +180,30 @@ impl Mul for u16 { } } +impl Mul for u32 { + type Output = Interval; + + fn mul(self, rhs: Interval) -> Interval { + rhs * self + } +} + +impl Mul for u64 { + type Output = Interval; + + fn mul(self, rhs: Interval) -> Interval { + rhs * self + } +} + +impl Mul for u128 { + type Output = Interval; + + fn mul(self, rhs: Interval) -> Interval { + rhs * self + } +} + impl Mul for f64 { type Output = Interval; @@ -231,6 +289,39 @@ impl Div for Interval { } } +impl Div for Interval { + type Output = Self; + + fn div(self, rhs: u32) -> Self { + match self { + Interval::Month(v) => Interval::Month(((v as u32) / rhs) as i32), + Interval::Microsecond(v) => Interval::Microsecond(((v as u32) / rhs) as i64), + } + } +} + +impl Div for Interval { + type Output = Self; + + fn div(self, rhs: u64) -> Self { + match self { + Interval::Month(v) => Interval::Month(((v as u64) / rhs) as i32), + Interval::Microsecond(v) => Interval::Microsecond(((v as u64) / rhs) as i64), + } + } +} + +impl Div for Interval { + type Output = Self; + + fn div(self, rhs: u128) -> Self { + match self { + Interval::Month(v) => Interval::Month(((v as u128) / rhs) as i32), + Interval::Microsecond(v) => Interval::Microsecond(((v as u128) / rhs) as i64), + } + } +} + impl Div for Interval { type Output = Self; @@ -318,6 +409,40 @@ impl Div for u16 { } } } + +impl Div for u32 { + type Output = Interval; + + fn div(self, rhs: Interval) -> Interval { + match rhs { + Interval::Month(v) => Interval::Month((self / (v as u32)) as i32), + Interval::Microsecond(v) => Interval::Microsecond((self / (v as u32)) as i64), + } + } +} + +impl Div for u64 { + type Output = Interval; + + fn div(self, rhs: Interval) -> Interval { + match rhs { + Interval::Month(v) => Interval::Month((self / (v as u64)) as i32), + Interval::Microsecond(v) => Interval::Microsecond((self / (v as u64)) as i64), + } + } +} + +impl Div for u128 { + type Output = Interval; + + fn div(self, rhs: Interval) -> Interval { + match rhs { + Interval::Month(v) => Interval::Month((self / (v as u128)) as i32), + Interval::Microsecond(v) => Interval::Microsecond((self / (v as u128)) as i64), + } + } +} + impl Div for f64 { type Output = Interval; diff --git a/core/src/data/value/binary_op/f64.rs b/core/src/data/value/binary_op/f64.rs index 287ac920c..797b53946 100644 --- a/core/src/data/value/binary_op/f64.rs +++ b/core/src/data/value/binary_op/f64.rs @@ -22,6 +22,9 @@ impl PartialEq for f64 { I128(rhs) => (lhs - (rhs as f64)).abs() < f64::EPSILON, U8(rhs) => (lhs - (rhs as f64)).abs() < f64::EPSILON, U16(rhs) => (lhs - (rhs as f64)).abs() < f64::EPSILON, + U32(rhs) => (lhs - (rhs as f64)).abs() < f64::EPSILON, + U64(rhs) => (lhs - (rhs as f64)).abs() < f64::EPSILON, + U128(rhs) => (lhs - (rhs as f64)).abs() < f64::EPSILON, F64(rhs) => (lhs - rhs).abs() < f64::EPSILON, Decimal(rhs) => Decimal::from_f64_retain(lhs) .map(|x| rhs == x) @@ -41,6 +44,9 @@ impl PartialOrd for f64 { I128(rhs) => self.partial_cmp(&(rhs as f64)), U8(rhs) => self.partial_cmp(&(rhs as f64)), U16(rhs) => self.partial_cmp(&(rhs as f64)), + U32(rhs) => self.partial_cmp(&(rhs as f64)), + U64(rhs) => self.partial_cmp(&(rhs as f64)), + U128(rhs) => self.partial_cmp(&(rhs as f64)), F64(rhs) => self.partial_cmp(&rhs), Decimal(rhs) => Decimal::from_f64_retain(*self) .map(|x| x.partial_cmp(&rhs)) @@ -64,6 +70,9 @@ impl TryBinaryOperator for f64 { I128(rhs) => Ok(F64(lhs + rhs as f64)), U8(rhs) => Ok(F64(lhs + rhs as f64)), U16(rhs) => Ok(F64(lhs + rhs as f64)), + U32(rhs) => Ok(F64(lhs + rhs as f64)), + U64(rhs) => Ok(F64(lhs + rhs as f64)), + U128(rhs) => Ok(F64(lhs + rhs as f64)), F64(rhs) => Ok(F64(lhs + rhs)), Decimal(rhs) => Decimal::from_f64_retain(lhs) .map(|x| Ok(Decimal(x + rhs))) @@ -89,6 +98,9 @@ impl TryBinaryOperator for f64 { I128(rhs) => Ok(F64(lhs - rhs as f64)), U8(rhs) => Ok(F64(lhs - rhs as f64)), U16(rhs) => Ok(F64(lhs - rhs as f64)), + U32(rhs) => Ok(F64(lhs - rhs as f64)), + U64(rhs) => Ok(F64(lhs - rhs as f64)), + U128(rhs) => Ok(F64(lhs - rhs as f64)), F64(rhs) => Ok(F64(lhs - rhs)), Decimal(rhs) => Decimal::from_f64_retain(lhs) .map(|x| Ok(Decimal(x - rhs))) @@ -114,6 +126,9 @@ impl TryBinaryOperator for f64 { I128(rhs) => Ok(F64(lhs * rhs as f64)), U8(rhs) => Ok(F64(lhs * rhs as f64)), U16(rhs) => Ok(F64(lhs * rhs as f64)), + U32(rhs) => Ok(F64(lhs * rhs as f64)), + U64(rhs) => Ok(F64(lhs * rhs as f64)), + U128(rhs) => Ok(F64(lhs * rhs as f64)), F64(rhs) => Ok(F64(lhs * rhs)), Interval(rhs) => Ok(Interval(lhs * rhs)), Decimal(rhs) => Decimal::from_f64_retain(lhs) @@ -140,6 +155,9 @@ impl TryBinaryOperator for f64 { I128(rhs) => Ok(F64(lhs / rhs as f64)), U8(rhs) => Ok(F64(lhs / rhs as f64)), U16(rhs) => Ok(F64(lhs / rhs as f64)), + U32(rhs) => Ok(F64(lhs / rhs as f64)), + U64(rhs) => Ok(F64(lhs / rhs as f64)), + U128(rhs) => Ok(F64(lhs / rhs as f64)), F64(rhs) => Ok(F64(lhs / rhs)), Decimal(rhs) => Decimal::from_f64_retain(lhs) .map(|x| Ok(Decimal(x * rhs))) @@ -165,6 +183,9 @@ impl TryBinaryOperator for f64 { I128(rhs) => Ok(F64(lhs % rhs as f64)), U8(rhs) => Ok(F64(lhs % rhs as f64)), U16(rhs) => Ok(F64(lhs % rhs as f64)), + U32(rhs) => Ok(F64(lhs % rhs as f64)), + U64(rhs) => Ok(F64(lhs % rhs as f64)), + U128(rhs) => Ok(F64(lhs % rhs as f64)), F64(rhs) => Ok(F64(lhs % rhs)), Decimal(rhs) => match Decimal::from_f64_retain(lhs) { Some(x) => x From f4232ad9f198ceb50a122673139b10978654a501 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 16:43:08 +0900 Subject: [PATCH 06/62] feat: add unsinged int to key and json rs --- core/src/data/key.rs | 24 ++++++++++++++++++++++++ core/src/data/value/json.rs | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 21e408998..9775a7513 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -32,6 +32,9 @@ pub enum Key { U8(u8), Decimal(Decimal), U16(u16), + U32(u32), + U64(u64), + U128(u128), Bool(bool), Str(String), Bytea(Vec), @@ -52,6 +55,9 @@ impl PartialOrd for Key { (Key::I64(l), Key::I64(r)) => Some(l.cmp(r)), (Key::U8(l), Key::U8(r)) => Some(l.cmp(r)), (Key::U16(l), Key::U16(r)) => Some(l.cmp(r)), + (Key::U32(l), Key::U32(r)) => Some(l.cmp(r)), + (Key::U64(l), Key::U64(r)) => Some(l.cmp(r)), + (Key::U128(l), Key::U128(r)) => Some(l.cmp(r)), (Key::Decimal(l), Key::Decimal(r)) => Some(l.cmp(r)), (Key::Bool(l), Key::Bool(r)) => Some(l.cmp(r)), (Key::Str(l), Key::Str(r)) => Some(l.cmp(r)), @@ -81,6 +87,9 @@ impl TryFrom for Key { I128(v) => Ok(Key::I128(v)), U8(v) => Ok(Key::U8(v)), U16(v) => Ok(Key::U16(v)), + U32(v) => Ok(Key::U32(v)), + U64(v) => Ok(Key::U64(v)), + U128(v) => Ok(Key::U128(v)), Decimal(v) => Ok(Key::Decimal(v)), Str(v) => Ok(Key::Str(v)), Bytea(v) => Ok(Key::Bytea(v)), @@ -174,6 +183,21 @@ impl Key { .chain(v.to_be_bytes().iter()) .copied() .collect::>(), + Key::U32(v) => [VALUE, 1] + .iter() + .chain(v.to_be_bytes().iter()) + .copied() + .collect::>(), + Key::U64(v) => [VALUE, 1] + .iter() + .chain(v.to_be_bytes().iter()) + .copied() + .collect::>(), + Key::U128(v) => [VALUE, 1] + .iter() + .chain(v.to_be_bytes().iter()) + .copied() + .collect::>(), Key::Decimal(v) => { let sign = if v.is_sign_positive() { 1 } else { 0 }; let convert = |v: Decimal| { diff --git a/core/src/data/value/json.rs b/core/src/data/value/json.rs index 52ea197a8..e92f8e8b5 100644 --- a/core/src/data/value/json.rs +++ b/core/src/data/value/json.rs @@ -47,6 +47,11 @@ impl TryFrom for JsonValue { .map_err(|_| ValueError::UnreachableJsonNumberParseFailure(v.to_string()).into()), Value::U8(v) => Ok(v.into()), Value::U16(v) => Ok(v.into()), + Value::U32(v) => Ok(v.into()), + Value::U64(v) => Ok(v.into()), + Value::U128(v)=> JsonNumber::from_str(&v.to_string()) + .map(JsonValue::Number) + .map_err(|_| ValueError::UnreachableJsonNumberParseFailure(v.to_string()).into()), Value::F64(v) => Ok(v.into()), Value::Decimal(v) => JsonNumber::from_str(&v.to_string()) .map(JsonValue::Number) From e6dcdc5917102ae58a4bf88e6f744de85ae4b879 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 16:57:06 +0900 Subject: [PATCH 07/62] fix: remove unreachable data type --- core/src/data/value/convert.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index e9373543f..98d2bdd3d 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -534,7 +534,6 @@ impl TryFrom<&Value> for u128 { | Value::Timestamp(_) | Value::Time(_) | Value::Interval(_) - | Value::Uuid(_) | Value::Map(_) | Value::List(_) | Value::Bytea(_) From e5f1b9c057171acd72aadf00cd029dc06519366e Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 17:00:31 +0900 Subject: [PATCH 08/62] style: thx fmt --- core/src/data/value/binary_op/integer/mod.rs | 4 ++-- core/src/data/value/convert.rs | 4 ++-- core/src/data/value/json.rs | 6 +++--- core/src/data/value/mod.rs | 7 ++++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/core/src/data/value/binary_op/integer/mod.rs b/core/src/data/value/binary_op/integer/mod.rs index 5d921fa59..11a6b7efb 100644 --- a/core/src/data/value/binary_op/integer/mod.rs +++ b/core/src/data/value/binary_op/integer/mod.rs @@ -3,10 +3,10 @@ mod i16; mod i32; mod i64; mod i8; +mod u128; mod u16; -mod u8; mod u32; mod u64; -mod u128; +mod u8; mod macros; diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 98d2bdd3d..46099728a 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -483,7 +483,7 @@ impl TryFrom<&Value> for u64 { Value::U16(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::U32(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::U64(value) => *value, - Value::U128(value) =>value.to_u64().ok_or(ValueError::ImpossibleCast)?, + Value::U128(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::F64(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() @@ -523,7 +523,7 @@ impl TryFrom<&Value> for u128 { Value::U16(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::U32(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::U64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, - Value::U128(value) =>*value, + Value::U128(value) => *value, Value::F64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() diff --git a/core/src/data/value/json.rs b/core/src/data/value/json.rs index e92f8e8b5..eb2eec713 100644 --- a/core/src/data/value/json.rs +++ b/core/src/data/value/json.rs @@ -49,9 +49,9 @@ impl TryFrom for JsonValue { Value::U16(v) => Ok(v.into()), Value::U32(v) => Ok(v.into()), Value::U64(v) => Ok(v.into()), - Value::U128(v)=> JsonNumber::from_str(&v.to_string()) - .map(JsonValue::Number) - .map_err(|_| ValueError::UnreachableJsonNumberParseFailure(v.to_string()).into()), + Value::U128(v) => JsonNumber::from_str(&v.to_string()) + .map(JsonValue::Number) + .map_err(|_| ValueError::UnreachableJsonNumberParseFailure(v.to_string()).into()), Value::F64(v) => Ok(v.into()), Value::Decimal(v) => JsonNumber::from_str(&v.to_string()) .map(JsonValue::Number) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 0e6ddb32b..ca67f710c 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -536,8 +536,8 @@ impl Value { use Value::*; match self { - I8(_) | I16(_) | I32(_) | I64(_) | I128(_) | U8(_) | U16(_) | U32(_)|U64(_)|U128(_)|F64(_) | Interval(_) - | Decimal(_) => Ok(self.clone()), + I8(_) | I16(_) | I32(_) | I64(_) | I128(_) | U8(_) | U16(_) | U32(_) | U64(_) + | U128(_) | F64(_) | Interval(_) | Decimal(_) => Ok(self.clone()), Null => Ok(Null), _ => Err(ValueError::UnaryPlusOnNonNumeric.into()), } @@ -635,7 +635,8 @@ impl Value { pub fn sqrt(&self) -> Result { use Value::*; match self { - I8(_) | I16(_) | I64(_) | I128(_) | U8(_) | U16(_) |U32(_)|U64(_)|U128(_)|F64(_) => { + I8(_) | I16(_) | I64(_) | I128(_) | U8(_) | U16(_) | U32(_) | U64(_) | U128(_) + | F64(_) => { let a: f64 = self.try_into()?; Ok(Value::F64(a.sqrt())) } From 9928766694211b69efdafb7b6bd3b9e0af8da636 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 17:22:58 +0900 Subject: [PATCH 09/62] test: improve coverage of macros --- .../data/value/binary_op/integer/macros.rs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/core/src/data/value/binary_op/integer/macros.rs b/core/src/data/value/binary_op/integer/macros.rs index 7d7734e3b..9599df8f2 100644 --- a/core/src/data/value/binary_op/integer/macros.rs +++ b/core/src/data/value/binary_op/integer/macros.rs @@ -259,6 +259,18 @@ macro_rules! generate_binary_op_tests { $primitive::MAX.try_add(&U16(1)), overflow_err($variant($primitive::MAX), U16(1), Add) ); + assert_eq!( + $primitive::MAX.try_add(&U32(1)), + overflow_err($variant($primitive::MAX), U32(1), Add) + ); + assert_eq!( + $primitive::MAX.try_add(&U64(1)), + overflow_err($variant($primitive::MAX), U64(1), Add) + ); + assert_eq!( + $primitive::MAX.try_add(&U128(1)), + overflow_err($variant($primitive::MAX), U128(1), Add) + ); } #[test] @@ -303,6 +315,18 @@ macro_rules! generate_binary_op_tests { $primitive::MIN.try_subtract(&U16(1)), overflow_err($variant($primitive::MIN), U16(1), Subtract) ); + assert_eq!( + $primitive::MIN.try_subtract(&U32(1)), + overflow_err($variant($primitive::MIN), U32(1), Subtract) + ); + assert_eq!( + $primitive::MIN.try_subtract(&U64(1)), + overflow_err($variant($primitive::MIN), U64(1), Subtract) + ); + assert_eq!( + $primitive::MIN.try_subtract(&U128(1)), + overflow_err($variant($primitive::MIN), U128(1), Subtract) + ); } #[test] @@ -347,6 +371,18 @@ macro_rules! generate_binary_op_tests { $primitive::MAX.try_multiply(&U16(2)), overflow_err($variant($primitive::MAX), U16(2), Multiply) ); + assert_eq!( + $primitive::MAX.try_multiply(&U32(2)), + overflow_err($variant($primitive::MAX), U32(2), Multiply) + ); + assert_eq!( + $primitive::MAX.try_multiply(&U64(2)), + overflow_err($variant($primitive::MAX), U64(2), Multiply) + ); + assert_eq!( + $primitive::MAX.try_multiply(&U128(2)), + overflow_err($variant($primitive::MAX), U128(2), Multiply) + ); } #[test] @@ -387,6 +423,18 @@ macro_rules! generate_binary_op_tests { $primitive::MAX.try_divide(&U16(0)), overflow_err($variant($primitive::MAX), U16(0), Divide) ); + assert_eq!( + $primitive::MAX.try_divide(&U32(0)), + overflow_err($variant($primitive::MAX), U32(0), Divide) + ); + assert_eq!( + $primitive::MAX.try_divide(&U64(0)), + overflow_err($variant($primitive::MAX), U64(0), Divide) + ); + assert_eq!( + $primitive::MAX.try_divide(&U128(0)), + overflow_err($variant($primitive::MAX), U128(0), Divide) + ); } #[test] @@ -427,6 +475,18 @@ macro_rules! generate_binary_op_tests { $primitive::MAX.try_modulo(&U16(0)), overflow_err($variant($primitive::MAX), U16(0), Modulo) ); + assert_eq!( + $primitive::MAX.try_modulo(&U32(0)), + overflow_err($variant($primitive::MAX), U32(0), Modulo) + ); + assert_eq!( + $primitive::MAX.try_modulo(&U64(0)), + overflow_err($variant($primitive::MAX), U64(0), Modulo) + ); + assert_eq!( + $primitive::MAX.try_modulo(&U128(0)), + overflow_err($variant($primitive::MAX), U128(0), Modulo) + ); } #[test] @@ -442,6 +502,9 @@ macro_rules! generate_binary_op_tests { assert_eq!(base.try_add(&I128(1)), Ok($variant(2))); assert_eq!(base.try_add(&U8(1)), Ok($variant(2))); assert_eq!(base.try_add(&U16(1)), Ok($variant(2))); + assert_eq!(base.try_add(&U32(1)), Ok($variant(2))); + assert_eq!(base.try_add(&U64(1)), Ok($variant(2))); + assert_eq!(base.try_add(&U128(1)), Ok($variant(2))); assert_eq!( base.try_add(&Bool(true)), @@ -467,6 +530,9 @@ macro_rules! generate_binary_op_tests { assert_eq!(base.try_subtract(&I128(1)), Ok($variant(0))); assert_eq!(base.try_subtract(&U8(1)), Ok($variant(0))); assert_eq!(base.try_subtract(&U16(1)), Ok($variant(0))); + assert_eq!(base.try_subtract(&U32(1)), Ok($variant(0))); + assert_eq!(base.try_subtract(&U64(1)), Ok($variant(0))); + assert_eq!(base.try_subtract(&U128(1)), Ok($variant(0))); assert_eq!( base.try_subtract(&Bool(true)), @@ -492,6 +558,9 @@ macro_rules! generate_binary_op_tests { assert_eq!(base.try_multiply(&I128(2)), Ok($variant(6))); assert_eq!(base.try_multiply(&U8(2)), Ok($variant(6))); assert_eq!(base.try_multiply(&U16(2)), Ok($variant(6))); + assert_eq!(base.try_multiply(&U32(2)), Ok($variant(6))); + assert_eq!(base.try_multiply(&U64(2)), Ok($variant(6))); + assert_eq!(base.try_multiply(&U128(2)), Ok($variant(6))); assert_eq!( base.try_multiply(&Bool(true)), @@ -517,6 +586,9 @@ macro_rules! generate_binary_op_tests { assert_eq!(base.try_divide(&I128(2)), Ok($variant(3))); assert_eq!(base.try_divide(&U8(2)), Ok($variant(3))); assert_eq!(base.try_divide(&U16(2)), Ok($variant(3))); + assert_eq!(base.try_divide(&U32(2)), Ok($variant(3))); + assert_eq!(base.try_divide(&U64(2)), Ok($variant(3))); + assert_eq!(base.try_divide(&U128(2)), Ok($variant(3))); assert_eq!( base.try_divide(&Bool(true)), @@ -542,6 +614,9 @@ macro_rules! generate_binary_op_tests { assert_eq!(base.try_modulo(&I128(1)), Ok($variant(0))); assert_eq!(base.try_modulo(&U8(1)), Ok($variant(0))); assert_eq!(base.try_modulo(&U16(1)), Ok($variant(0))); + assert_eq!(base.try_modulo(&U32(1)), Ok($variant(0))); + assert_eq!(base.try_modulo(&U64(1)), Ok($variant(0))); + assert_eq!(base.try_modulo(&U128(1)), Ok($variant(0))); assert_eq!( base.try_modulo(&Bool(true)), @@ -614,6 +689,9 @@ macro_rules! generate_cmp_ord_tests { assert_eq!(base, I128(1)); assert_eq!(base, U8(1)); assert_eq!(base, U16(1)); + assert_eq!(base, U32(1)); + assert_eq!(base, U64(1)); + assert_eq!(base, U128(1)); assert_ne!(base, Bool(true)); } @@ -634,6 +712,9 @@ macro_rules! generate_cmp_ord_tests { assert_eq!(base.partial_cmp(&I128(0)), Some(Ordering::Greater)); assert_eq!(base.partial_cmp(&U8(0)), Some(Ordering::Greater)); assert_eq!(base.partial_cmp(&U16(0)), Some(Ordering::Greater)); + assert_eq!(base.partial_cmp(&U32(0)), Some(Ordering::Greater)); + assert_eq!(base.partial_cmp(&U64(0)), Some(Ordering::Greater)); + assert_eq!(base.partial_cmp(&U128(0)), Some(Ordering::Greater)); assert_eq!( base.partial_cmp(&Decimal(Decimal::ONE)), @@ -647,6 +728,9 @@ macro_rules! generate_cmp_ord_tests { assert_eq!(base.partial_cmp(&I128(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&U8(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&U16(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U32(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U64(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U128(1)), Some(Ordering::Equal)); assert_eq!( base.partial_cmp(&Decimal(Decimal::TWO)), @@ -660,6 +744,9 @@ macro_rules! generate_cmp_ord_tests { assert_eq!(base.partial_cmp(&I128(2)), Some(Ordering::Less)); assert_eq!(base.partial_cmp(&U8(2)), Some(Ordering::Less)); assert_eq!(base.partial_cmp(&U16(2)), Some(Ordering::Less)); + assert_eq!(base.partial_cmp(&U32(2)), Some(Ordering::Less)); + assert_eq!(base.partial_cmp(&U64(2)), Some(Ordering::Less)); + assert_eq!(base.partial_cmp(&U128(2)), Some(Ordering::Less)); assert_eq!(base.partial_cmp(&Bool(true)), None); } From f285763f2f30fc79fe825a04742b650e76a71614 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 17:45:31 +0900 Subject: [PATCH 10/62] test: improve coverage of json.rs --- core/src/data/value/json.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/data/value/json.rs b/core/src/data/value/json.rs index eb2eec713..afd7c70b7 100644 --- a/core/src/data/value/json.rs +++ b/core/src/data/value/json.rs @@ -155,6 +155,18 @@ mod tests { Value::U16(100).try_into(), Ok(JsonValue::Number(100.into())) ); + assert_eq!( + Value::U32(100).try_into(), + Ok(JsonValue::Number(100.into())) + ); + assert_eq!( + Value::U64(100).try_into(), + Ok(JsonValue::Number(100.into())) + ); + assert_eq!( + Value::U128(100).try_into(), + Ok(JsonValue::Number(100.into())) + ); assert!(JsonValue::try_from(Value::I128(i128::MAX)).is_ok()); assert_eq!( From 1a296e38b57cb4c6f537ff1a9e0fe4d444baaa6c Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 19:21:52 +0900 Subject: [PATCH 11/62] test: improve line coverage of convert.rs --- core/src/data/value/convert.rs | 215 +++++++++++++++++++++++++++++++-- 1 file changed, 208 insertions(+), 7 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 46099728a..6465665ae 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -675,7 +675,7 @@ macro_rules! try_from_owned_value { )*} } -try_from_owned_value!(bool, i8, i16, i32, i64, i128, f64, u8, u16, u128, usize, Decimal); +try_from_owned_value!(bool, i8, i16, i32, i64, i128, f64, u8, u16,u32,u64, u128, usize, Decimal); impl TryFrom<&Value> for NaiveDate { type Error = Error; @@ -782,6 +782,9 @@ mod tests { test!(Value::I128(1234567890), "1234567890"); test!(Value::U8(122), "122"); test!(Value::U16(122), "122"); + test!(Value::U32(122), "122"); + test!(Value::U64(122), "122"); + test!(Value::U128(122), "122"); test!(Value::F64(1234567890.0987), "1234567890.0987"); test!(Value::Date(date(2021, 11, 20)), "2021-11-20"); test!( @@ -825,6 +828,12 @@ mod tests { test!(Value::U8(2), Err(ValueError::ImpossibleCast.into())); test!(Value::U16(1), Ok(true)); test!(Value::U16(0), Ok(false)); + test!(Value::U32(1), Ok(true)); + test!(Value::U32(0), Ok(false)); + test!(Value::U64(1), Ok(true)); + test!(Value::U64(0), Ok(false)); + test!(Value::U128(1), Ok(true)); + test!(Value::U128(0), Ok(false)); test!(Value::U16(2), Err(ValueError::ImpossibleCast.into())); test!(Value::F64(1.0), Ok(true)); test!(Value::F64(0.0), Ok(false)); @@ -892,6 +901,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::F64(122.0), Ok(122)); test!(Value::F64(122.9), Ok(122)); test!(Value::Str("122".to_owned()), Ok(122)); @@ -933,6 +945,9 @@ mod tests { test!(Value::I128(128), Err(ValueError::ImpossibleCast.into())); test!(Value::U8(128), Err(ValueError::ImpossibleCast.into())); test!(Value::U16(128), Err(ValueError::ImpossibleCast.into())); + test!(Value::U32(128), Err(ValueError::ImpossibleCast.into())); + test!(Value::U64(128), Err(ValueError::ImpossibleCast.into())); + test!(Value::U128(128), Err(ValueError::ImpossibleCast.into())); test!(Value::F64(128.0), Err(ValueError::ImpossibleCast.into())); } @@ -954,6 +969,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::F64(122.0), Ok(122)); test!(Value::F64(122.1), Ok(122)); test!(Value::Str("122".to_owned()), Ok(122)); @@ -1007,6 +1025,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::I64(1234567890), Ok(1234567890)); test!(Value::F64(1234567890.0), Ok(1234567890)); test!(Value::F64(1234567890.1), Ok(1234567890)); @@ -1061,6 +1082,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::I64(1234567890), Ok(1234567890)); test!(Value::F64(1234567890.0), Ok(1234567890)); test!(Value::F64(1234567890.1), Ok(1234567890)); @@ -1115,6 +1139,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::I64(1234567890), Ok(1234567890)); test!(Value::F64(1234567890.0), Ok(1234567890)); test!(Value::F64(1234567890.9), Ok(1234567890)); @@ -1169,6 +1196,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::F64(122.0), Ok(122)); test!(Value::F64(122.9), Ok(122)); test!(Value::Str("122".to_owned()), Ok(122)); @@ -1234,6 +1264,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::F64(122.0), Ok(122)); test!(Value::F64(122.1), Ok(122)); test!(Value::Str("122".to_owned()), Ok(122)); @@ -1269,6 +1302,177 @@ mod tests { test!(Value::Null, Err(ValueError::ImpossibleCast.into())); } + #[test] + fn try_into_u32() { + macro_rules! test { + ($from: expr, $to: expr) => { + assert_eq!($from.try_into() as Result, $to); + assert_eq!(u32::try_from($from), $to); + }; + } + + test!(Value::Bool(true), Ok(1)); + test!(Value::Bool(false), Ok(0)); + test!(Value::I8(122), Ok(122)); + test!(Value::I16(122), Ok(122)); + test!(Value::I32(122), Ok(122)); + test!(Value::I64(122), Ok(122)); + test!(Value::I128(122), Ok(122)); + test!(Value::U8(122), Ok(122)); + test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); + test!(Value::F64(122.0), Ok(122)); + test!(Value::F64(122.1), Ok(122)); + test!(Value::Str("122".to_owned()), Ok(122)); + test!(Value::Decimal(Decimal::new(122, 0)), Ok(122)); + test!( + Value::Date(date(2021, 11, 20)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Timestamp(timestamp(2021, 11, 20, 10, 0, 0, 0)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Time(time(10, 0, 0, 0)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Interval(I::Month(1)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Uuid(195965723427462096757863453463987888808), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Map(HashMap::new()), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::List(Vec::new()), + Err(ValueError::ImpossibleCast.into()) + ); + test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + } + + #[test] + fn try_into_u64() { + macro_rules! test { + ($from: expr, $to: expr) => { + assert_eq!($from.try_into() as Result, $to); + assert_eq!(u64::try_from($from), $to); + }; + } + + test!(Value::Bool(true), Ok(1)); + test!(Value::Bool(false), Ok(0)); + test!(Value::I8(122), Ok(122)); + test!(Value::I16(122), Ok(122)); + test!(Value::I32(122), Ok(122)); + test!(Value::I64(122), Ok(122)); + test!(Value::I128(122), Ok(122)); + test!(Value::U8(122), Ok(122)); + test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); + test!(Value::F64(122.0), Ok(122)); + test!(Value::F64(122.1), Ok(122)); + test!(Value::Str("122".to_owned()), Ok(122)); + test!(Value::Decimal(Decimal::new(122, 0)), Ok(122)); + test!( + Value::Date(date(2021, 11, 20)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Timestamp(timestamp(2021, 11, 20, 10, 0, 0, 0)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Time(time(10, 0, 0, 0)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Interval(I::Month(1)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Uuid(195965723427462096757863453463987888808), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Map(HashMap::new()), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::List(Vec::new()), + Err(ValueError::ImpossibleCast.into()) + ); + test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + } + + #[test] + fn try_into_u128() { + macro_rules! test { + ($from: expr, $to: expr) => { + assert_eq!($from.try_into() as Result, $to); + assert_eq!(u128::try_from($from), $to); + }; + } + + test!(Value::Bool(true), Ok(1)); + test!(Value::Bool(false), Ok(0)); + test!(Value::I8(122), Ok(122)); + test!(Value::I16(122), Ok(122)); + test!(Value::I32(122), Ok(122)); + test!(Value::I64(122), Ok(122)); + test!(Value::I128(122), Ok(122)); + test!(Value::U8(122), Ok(122)); + test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); + test!(Value::F64(122.0), Ok(122)); + test!(Value::F64(122.1), Ok(122)); + test!(Value::Str("122".to_owned()), Ok(122)); + test!(Value::Decimal(Decimal::new(122, 0)), Ok(122)); + test!( + Value::Date(date(2021, 11, 20)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Timestamp(timestamp(2021, 11, 20, 10, 0, 0, 0)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Time(time(10, 0, 0, 0)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Interval(I::Month(1)), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Uuid(195965723427462096757863453463987888808), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::Map(HashMap::new()), + Err(ValueError::ImpossibleCast.into()) + ); + test!( + Value::List(Vec::new()), + Err(ValueError::ImpossibleCast.into()) + ); + test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + let uuid = 195965723427462096757863453463987888808; + assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); + assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); + } + #[test] fn try_into_f64() { macro_rules! test { @@ -1287,6 +1491,9 @@ mod tests { test!(Value::I128(122), Ok(122.0)); test!(Value::U8(122), Ok(122.0)); test!(Value::U16(122), Ok(122.0)); + test!(Value::U32(122), Ok(122.0)); + test!(Value::U64(122), Ok(122.0)); + test!(Value::U128(122), Ok(122.0)); test!(Value::I64(1234567890), Ok(1234567890.0)); test!(Value::F64(1234567890.1), Ok(1234567890.1)); test!(Value::Str("1234567890.1".to_owned()), Ok(1234567890.1)); @@ -1445,10 +1652,4 @@ mod tests { ); } - #[test] - fn try_into_u128() { - let uuid = 195965723427462096757863453463987888808; - assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); - assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); - } } From 6a93f2e2a0c17965667f4731005231e2e1c1d73e Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 19:25:51 +0900 Subject: [PATCH 12/62] style: thx fmt --- core/src/data/value/convert.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 6465665ae..53e870fd3 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -675,7 +675,7 @@ macro_rules! try_from_owned_value { )*} } -try_from_owned_value!(bool, i8, i16, i32, i64, i128, f64, u8, u16,u32,u64, u128, usize, Decimal); +try_from_owned_value!(bool, i8, i16, i32, i64, i128, f64, u8, u16, u32, u64, u128, usize, Decimal); impl TryFrom<&Value> for NaiveDate { type Error = Error; @@ -1651,5 +1651,4 @@ mod tests { Ok(I::Month(274)) ); } - } From 7ee58e470a1ff70b7d9cd6f41d2e09aec35f5745 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 26 Nov 2022 19:30:09 +0900 Subject: [PATCH 13/62] test: remove misleading test case --- core/src/data/value/convert.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 53e870fd3..7831f444e 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -1455,10 +1455,6 @@ mod tests { Value::Interval(I::Month(1)), Err(ValueError::ImpossibleCast.into()) ); - test!( - Value::Uuid(195965723427462096757863453463987888808), - Err(ValueError::ImpossibleCast.into()) - ); test!( Value::Map(HashMap::new()), Err(ValueError::ImpossibleCast.into()) From 2cd2f89e73f25b9bae4518a4503c7089024feb43 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:05:59 +0900 Subject: [PATCH 14/62] test: improve line coverage --- core/src/data/value/expr.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/data/value/expr.rs b/core/src/data/value/expr.rs index 696b27cb8..378b11f4a 100644 --- a/core/src/data/value/expr.rs +++ b/core/src/data/value/expr.rs @@ -174,6 +174,19 @@ mod tests { BigDecimal::from_u8(8).unwrap() ))) ); + assert_eq!( + Value::U16(16).try_into(), + Ok(Expr::Literal(AstLiteral::Number( + BigDecimal::from_u16(16).unwrap() + ))) + ); + assert_eq!( + Value::U32(32).try_into(), + Ok(Expr::Literal(AstLiteral::Number( + BigDecimal::from_u32(32).unwrap() + ))) + ); + assert_eq!( Value::F64(64.4).try_into(), Ok(Expr::Literal(AstLiteral::Number( From bc2a1efaf787a0b066e8c5028d6c0143a35f32da Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:23:03 +0900 Subject: [PATCH 15/62] style: thx clippy --- core/src/data/value/convert.rs | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 8e9217f05..95feeda7f 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -385,13 +385,7 @@ impl TryFrom<&Value> for u32 { fn try_from(v: &Value) -> Result { Ok(match v { - Value::Bool(value) => { - if *value { - 1 - } else { - 0 - } - } + Value::Bool(value) => u32::from(*value), Value::I8(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, Value::I16(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, Value::I32(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, @@ -425,13 +419,7 @@ impl TryFrom<&Value> for u64 { fn try_from(v: &Value) -> Result { Ok(match v { - Value::Bool(value) => { - if *value { - 1 - } else { - 0 - } - } + Value::Bool(value) => u64,from(*value), Value::I8(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::I16(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::I32(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, @@ -465,13 +453,7 @@ impl TryFrom<&Value> for u128 { fn try_from(v: &Value) -> Result { Ok(match v { - Value::Bool(value) => { - if *value { - 1 - } else { - 0 - } - } + Value::Bool(value) => u128.from(*values), Value::I8(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::I16(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::I32(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, From 29893a7a6401d932ec53cfe08cc3c116a34b7ec3 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:28:54 +0900 Subject: [PATCH 16/62] fix: fix misleading expression --- core/src/data/value/convert.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 95feeda7f..7e6a751ab 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -419,7 +419,7 @@ impl TryFrom<&Value> for u64 { fn try_from(v: &Value) -> Result { Ok(match v { - Value::Bool(value) => u64,from(*value), + Value::Bool(value) => u64::from(*value), Value::I8(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::I16(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, Value::I32(value) => value.to_u64().ok_or(ValueError::ImpossibleCast)?, @@ -453,7 +453,7 @@ impl TryFrom<&Value> for u128 { fn try_from(v: &Value) -> Result { Ok(match v { - Value::Bool(value) => u128.from(*values), + Value::Bool(value) => u128::from(*values), Value::I8(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::I16(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::I32(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, From 4de73eee6fa40e0816cb52267be6641aee8628d5 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:33:54 +0900 Subject: [PATCH 17/62] fix : fix typo --- core/src/data/value/convert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 7e6a751ab..37efafb47 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -453,7 +453,7 @@ impl TryFrom<&Value> for u128 { fn try_from(v: &Value) -> Result { Ok(match v { - Value::Bool(value) => u128::from(*values), + Value::Bool(value) => u128::from(*value), Value::I8(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::I16(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::I32(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, From 0ef97815870e550c7e62a8f897ab045faaa44273 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:44:33 +0900 Subject: [PATCH 18/62] test : improve line coverage of `expr.rs` --- core/src/data/value/expr.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/data/value/expr.rs b/core/src/data/value/expr.rs index 378b11f4a..6450cf152 100644 --- a/core/src/data/value/expr.rs +++ b/core/src/data/value/expr.rs @@ -186,6 +186,18 @@ mod tests { BigDecimal::from_u32(32).unwrap() ))) ); + assert_eq!( + Value::U64(64).try_into(), + Ok(Expr::Literal(AstLiteral::Number( + BigDecimal::from_u64(64).unwrap() + ))) + ); + assert_eq!( + Value::U128(128).try_into(), + Ok(Expr::Literal(AstLiteral::Number( + BigDecimal::from_u32(128).unwrap() + ))) + ); assert_eq!( Value::F64(64.4).try_into(), From 64e5f39797a5a076b694b315ab125d651f850b37 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:53:18 +0900 Subject: [PATCH 19/62] test: improve coverage of `bigdecimal_ext.rs` --- core/src/data/bigdecimal_ext.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/data/bigdecimal_ext.rs b/core/src/data/bigdecimal_ext.rs index a92edc96b..653c5c5f6 100644 --- a/core/src/data/bigdecimal_ext.rs +++ b/core/src/data/bigdecimal_ext.rs @@ -52,12 +52,16 @@ impl BigDecimalExt for BigDecimal { .then(|| bigdecimal::ToPrimitive::to_u32(self))? } fn to_u64(&self) -> Option { - self.is_integer() - .then(|| bigdecimal::ToPrimitive::to_u64(self))? + match self.is_integer(){ + true => bigdecimal::ToPrimitive::to_u64(self), + false => None, + } } fn to_u128(&self) -> Option { - self.is_integer() - .then(|| bigdecimal::ToPrimitive::to_u128(self))? + match self.is_integer(){ + true => bigdecimal::ToPrimitive::to_u128(self), + false => None, + } } fn to_f64(&self) -> Option { bigdecimal::ToPrimitive::to_f64(self) From 52ee74e470c70f243a8fb54fc3c48287f9cfa34e Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 14:54:34 +0900 Subject: [PATCH 20/62] style: cargo fmt --- core/src/data/bigdecimal_ext.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/data/bigdecimal_ext.rs b/core/src/data/bigdecimal_ext.rs index 653c5c5f6..0e73813d4 100644 --- a/core/src/data/bigdecimal_ext.rs +++ b/core/src/data/bigdecimal_ext.rs @@ -52,13 +52,13 @@ impl BigDecimalExt for BigDecimal { .then(|| bigdecimal::ToPrimitive::to_u32(self))? } fn to_u64(&self) -> Option { - match self.is_integer(){ + match self.is_integer() { true => bigdecimal::ToPrimitive::to_u64(self), false => None, } } fn to_u128(&self) -> Option { - match self.is_integer(){ + match self.is_integer() { true => bigdecimal::ToPrimitive::to_u128(self), false => None, } From 580776496e796ec4d0bedbe2bb0403a68b822df9 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 15:27:38 +0900 Subject: [PATCH 21/62] test: improve line coverage of `value/mod.rs` --- core/src/data/value/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 05d43da75..3514a4972 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1717,6 +1717,9 @@ mod tests { assert_eq!(I128(5).unary_factorial(), Ok(I128(120))); assert_eq!(U8(5).unary_factorial(), Ok(I128(120))); assert_eq!(U16(5).unary_factorial(), Ok(I128(120))); + assert_eq!(U32(5).unary_factorial(),Ok(I128(120))); + assert_eq!(U64(5).unary_factorial(),Ok(I128(120))); + assert_eq!(U128(5).unary_factorial(),Ok(I128(120))); assert_eq!( F64(5.0).unary_factorial(), Err(ValueError::FactorialOnNonInteger.into()) @@ -1736,6 +1739,9 @@ mod tests { assert_eq!(I128(9).sqrt(), Ok(F64(3.0))); assert_eq!(U8(9).sqrt(), Ok(F64(3.0))); assert_eq!(U16(9).sqrt(), Ok(F64(3.0))); + assert_eq!(U32(9).sqrt(),Ok(F64(3.0))); + assert_eq!(U64(9).sqrt(),Ok(F64(3.0))); + assert_eq!(U128(9).sqrt(),Ok(F64(3.0))); assert_eq!(F64(9.0).sqrt(), Ok(F64(3.0))); assert!(Null.sqrt().unwrap().is_null()); assert_eq!( @@ -1796,6 +1802,9 @@ mod tests { assert_eq!(I128(1).get_type(), Some(D::Int128)); assert_eq!(U8(1).get_type(), Some(D::Uint8)); assert_eq!(U16(1).get_type(), Some(D::Uint16)); + assert_eq!(U32(1).get_type(),Some(D::Uint32)); + assert_eq!(U64(1).get_type(),Some(D::Uint64)); + assert_eq!(U128(1).get_type(),Some(D::Uint128)); assert_eq!(F64(1.1).get_type(), Some(D::Float)); assert_eq!(decimal.get_type(), Some(D::Decimal)); assert_eq!(Bool(true).get_type(), Some(D::Boolean)); From 33b2c0f8b25b316bb805646e0b5718da102e9faa Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 15:30:12 +0900 Subject: [PATCH 22/62] style : cargo fmt --- core/src/data/value/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 3514a4972..134a4ced7 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1717,9 +1717,9 @@ mod tests { assert_eq!(I128(5).unary_factorial(), Ok(I128(120))); assert_eq!(U8(5).unary_factorial(), Ok(I128(120))); assert_eq!(U16(5).unary_factorial(), Ok(I128(120))); - assert_eq!(U32(5).unary_factorial(),Ok(I128(120))); - assert_eq!(U64(5).unary_factorial(),Ok(I128(120))); - assert_eq!(U128(5).unary_factorial(),Ok(I128(120))); + assert_eq!(U32(5).unary_factorial(), Ok(I128(120))); + assert_eq!(U64(5).unary_factorial(), Ok(I128(120))); + assert_eq!(U128(5).unary_factorial(), Ok(I128(120))); assert_eq!( F64(5.0).unary_factorial(), Err(ValueError::FactorialOnNonInteger.into()) @@ -1739,9 +1739,9 @@ mod tests { assert_eq!(I128(9).sqrt(), Ok(F64(3.0))); assert_eq!(U8(9).sqrt(), Ok(F64(3.0))); assert_eq!(U16(9).sqrt(), Ok(F64(3.0))); - assert_eq!(U32(9).sqrt(),Ok(F64(3.0))); - assert_eq!(U64(9).sqrt(),Ok(F64(3.0))); - assert_eq!(U128(9).sqrt(),Ok(F64(3.0))); + assert_eq!(U32(9).sqrt(), Ok(F64(3.0))); + assert_eq!(U64(9).sqrt(), Ok(F64(3.0))); + assert_eq!(U128(9).sqrt(), Ok(F64(3.0))); assert_eq!(F64(9.0).sqrt(), Ok(F64(3.0))); assert!(Null.sqrt().unwrap().is_null()); assert_eq!( @@ -1802,9 +1802,9 @@ mod tests { assert_eq!(I128(1).get_type(), Some(D::Int128)); assert_eq!(U8(1).get_type(), Some(D::Uint8)); assert_eq!(U16(1).get_type(), Some(D::Uint16)); - assert_eq!(U32(1).get_type(),Some(D::Uint32)); - assert_eq!(U64(1).get_type(),Some(D::Uint64)); - assert_eq!(U128(1).get_type(),Some(D::Uint128)); + assert_eq!(U32(1).get_type(), Some(D::Uint32)); + assert_eq!(U64(1).get_type(), Some(D::Uint64)); + assert_eq!(U128(1).get_type(), Some(D::Uint128)); assert_eq!(F64(1.1).get_type(), Some(D::Float)); assert_eq!(decimal.get_type(), Some(D::Decimal)); assert_eq!(Bool(true).get_type(), Some(D::Boolean)); From 884a16c9826bc35209e49836155a2375dfad1ce4 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 22:34:54 +0900 Subject: [PATCH 23/62] test: improve line coverage of `value/mod.rs` --- core/src/data/value/mod.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 134a4ced7..4f7653ad5 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1643,6 +1643,12 @@ mod tests { assert!(U8(1).validate_type(&D::Text).is_err()); assert!(U16(1).validate_type(&D::Uint16).is_ok()); assert!(U16(1).validate_type(&D::Text).is_err()); + assert!(U32(1).validate_type(&D::Uint32).is_ok()); + assert!(U32(1).validate_type(&D::Text).is_err()); + assert!(U64(1).validate_type(&D::Uint64).is_ok()); + assert!(U64(1).validate_type(&D::Text).is_err()); + assert!(U128(1).validate_type(&D::Uint128).is_ok()); + assert!(U128(1).validate_type(&D::Text).is_err()); assert!(F64(1.0).validate_type(&D::Float).is_ok()); assert!(F64(1.0).validate_type(&D::Int).is_err()); assert!(Decimal(rust_decimal::Decimal::ONE) @@ -1717,9 +1723,9 @@ mod tests { assert_eq!(I128(5).unary_factorial(), Ok(I128(120))); assert_eq!(U8(5).unary_factorial(), Ok(I128(120))); assert_eq!(U16(5).unary_factorial(), Ok(I128(120))); - assert_eq!(U32(5).unary_factorial(), Ok(I128(120))); - assert_eq!(U64(5).unary_factorial(), Ok(I128(120))); - assert_eq!(U128(5).unary_factorial(), Ok(I128(120))); + assert_eq!(U32(5).unary_factorial(),Ok(I128(120))); + assert_eq!(U64(5).unary_factorial(),Ok(I128(120))); + assert_eq!(U128(5).unary_factorial(),Ok(I128(120))); assert_eq!( F64(5.0).unary_factorial(), Err(ValueError::FactorialOnNonInteger.into()) @@ -1739,9 +1745,9 @@ mod tests { assert_eq!(I128(9).sqrt(), Ok(F64(3.0))); assert_eq!(U8(9).sqrt(), Ok(F64(3.0))); assert_eq!(U16(9).sqrt(), Ok(F64(3.0))); - assert_eq!(U32(9).sqrt(), Ok(F64(3.0))); - assert_eq!(U64(9).sqrt(), Ok(F64(3.0))); - assert_eq!(U128(9).sqrt(), Ok(F64(3.0))); + assert_eq!(U32(9).sqrt(),Ok(F64(3.0))); + assert_eq!(U64(9).sqrt(),Ok(F64(3.0))); + assert_eq!(U128(9).sqrt(),Ok(F64(3.0))); assert_eq!(F64(9.0).sqrt(), Ok(F64(3.0))); assert!(Null.sqrt().unwrap().is_null()); assert_eq!( @@ -1802,9 +1808,9 @@ mod tests { assert_eq!(I128(1).get_type(), Some(D::Int128)); assert_eq!(U8(1).get_type(), Some(D::Uint8)); assert_eq!(U16(1).get_type(), Some(D::Uint16)); - assert_eq!(U32(1).get_type(), Some(D::Uint32)); - assert_eq!(U64(1).get_type(), Some(D::Uint64)); - assert_eq!(U128(1).get_type(), Some(D::Uint128)); + assert_eq!(U32(1).get_type(),Some(D::Uint32)); + assert_eq!(U64(1).get_type(),Some(D::Uint64)); + assert_eq!(U128(1).get_type(),Some(D::Uint128)); assert_eq!(F64(1.1).get_type(), Some(D::Float)); assert_eq!(decimal.get_type(), Some(D::Decimal)); assert_eq!(Bool(true).get_type(), Some(D::Boolean)); From e994a0acc9a5ac7b33d6e85953854067fbe4e9b1 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 22:38:01 +0900 Subject: [PATCH 24/62] style: cargo fmt --- core/src/data/value/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 4f7653ad5..0352fb2ce 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1723,9 +1723,9 @@ mod tests { assert_eq!(I128(5).unary_factorial(), Ok(I128(120))); assert_eq!(U8(5).unary_factorial(), Ok(I128(120))); assert_eq!(U16(5).unary_factorial(), Ok(I128(120))); - assert_eq!(U32(5).unary_factorial(),Ok(I128(120))); - assert_eq!(U64(5).unary_factorial(),Ok(I128(120))); - assert_eq!(U128(5).unary_factorial(),Ok(I128(120))); + assert_eq!(U32(5).unary_factorial(), Ok(I128(120))); + assert_eq!(U64(5).unary_factorial(), Ok(I128(120))); + assert_eq!(U128(5).unary_factorial(), Ok(I128(120))); assert_eq!( F64(5.0).unary_factorial(), Err(ValueError::FactorialOnNonInteger.into()) @@ -1745,9 +1745,9 @@ mod tests { assert_eq!(I128(9).sqrt(), Ok(F64(3.0))); assert_eq!(U8(9).sqrt(), Ok(F64(3.0))); assert_eq!(U16(9).sqrt(), Ok(F64(3.0))); - assert_eq!(U32(9).sqrt(),Ok(F64(3.0))); - assert_eq!(U64(9).sqrt(),Ok(F64(3.0))); - assert_eq!(U128(9).sqrt(),Ok(F64(3.0))); + assert_eq!(U32(9).sqrt(), Ok(F64(3.0))); + assert_eq!(U64(9).sqrt(), Ok(F64(3.0))); + assert_eq!(U128(9).sqrt(), Ok(F64(3.0))); assert_eq!(F64(9.0).sqrt(), Ok(F64(3.0))); assert!(Null.sqrt().unwrap().is_null()); assert_eq!( @@ -1808,9 +1808,9 @@ mod tests { assert_eq!(I128(1).get_type(), Some(D::Int128)); assert_eq!(U8(1).get_type(), Some(D::Uint8)); assert_eq!(U16(1).get_type(), Some(D::Uint16)); - assert_eq!(U32(1).get_type(),Some(D::Uint32)); - assert_eq!(U64(1).get_type(),Some(D::Uint64)); - assert_eq!(U128(1).get_type(),Some(D::Uint128)); + assert_eq!(U32(1).get_type(), Some(D::Uint32)); + assert_eq!(U64(1).get_type(), Some(D::Uint64)); + assert_eq!(U128(1).get_type(), Some(D::Uint128)); assert_eq!(F64(1.1).get_type(), Some(D::Float)); assert_eq!(decimal.get_type(), Some(D::Decimal)); assert_eq!(Bool(true).get_type(), Some(D::Boolean)); From 4cf9eff2d11eab69b01087b60d0a3a72302837de Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 23:01:19 +0900 Subject: [PATCH 25/62] test: improve line coverage of `f64.rs` --- core/src/data/value/binary_op/f64.rs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/core/src/data/value/binary_op/f64.rs b/core/src/data/value/binary_op/f64.rs index 797b53946..adfc8b9f4 100644 --- a/core/src/data/value/binary_op/f64.rs +++ b/core/src/data/value/binary_op/f64.rs @@ -232,6 +232,9 @@ mod tests { assert_eq!(base, I128(1)); assert_eq!(base, U8(1)); assert_eq!(base, U16(1)); + assert_eq!(base, U32(1)); + assert_eq!(base, U64(1)); + assert_eq!(base, U128(1)); assert_eq!(base, F64(1.0)); assert_eq!(base, Decimal(Decimal::from(1))); @@ -249,6 +252,9 @@ mod tests { assert_eq!(base.partial_cmp(&I128(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&U8(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&U16(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U32(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U64(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U128(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&F64(1.0)), Some(Ordering::Equal)); assert_eq!( base.partial_cmp(&Decimal(Decimal::ONE)), @@ -269,6 +275,9 @@ mod tests { assert!(matches!(base.try_add(&I128(1)), Ok(F64(x)) if (x - 2.0).abs() < f64::EPSILON )); assert!(matches!(base.try_add(&U8(1)), Ok(F64(x)) if (x - 2.0).abs() < f64::EPSILON )); assert!(matches!(base.try_add(&U16(1)), Ok(F64(x)) if (x - 2.0).abs() < f64::EPSILON )); + assert!(matches!(base.try_add(&U32(1)),Ok(F64(x)) if (x-2.0).abs() < f64::EPSILON)); + assert!(matches!(base.try_add(&U64(1)),Ok(F64(x)) if (x-2.0).abs() < f64::EPSILON)); + assert!(matches!(base.try_add(&U128(1)),Ok(F64(x)) if (x-2.0).abs() Date: Fri, 13 Jan 2023 23:02:45 +0900 Subject: [PATCH 26/62] style: cargo fmt --- core/src/data/value/binary_op/f64.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/data/value/binary_op/f64.rs b/core/src/data/value/binary_op/f64.rs index adfc8b9f4..a9419d17d 100644 --- a/core/src/data/value/binary_op/f64.rs +++ b/core/src/data/value/binary_op/f64.rs @@ -365,11 +365,13 @@ mod tests { assert!( matches!(base.try_multiply(&U16(1)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON ) ); - assert!( + assert!( matches!(base.try_multiply(&U32(1)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON ) - ); assert!( + ); + assert!( matches!(base.try_multiply(&U64(1)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON ) - ); assert!( + ); + assert!( matches!(base.try_multiply(&U128(1)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON ) ); assert!( @@ -405,8 +407,6 @@ mod tests { assert!(matches!(base.try_divide(&U64(1)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON )); assert!(matches!(base.try_divide(&U128(1)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON )); - - assert!( matches!(base.try_divide(&F64(1.0)), Ok(F64(x)) if (x - 1.0).abs() < f64::EPSILON ) ); From 67d4cf64aea79a24bef6bb3e9839da3cd8182180 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 23:52:19 +0900 Subject: [PATCH 27/62] test: improve line coverage of `decimal.rs` --- core/src/data/value/binary_op/decimal.rs | 284 ++++++++++++++++++++++- 1 file changed, 283 insertions(+), 1 deletion(-) diff --git a/core/src/data/value/binary_op/decimal.rs b/core/src/data/value/binary_op/decimal.rs index 5a26b7dd2..ecf1ec180 100644 --- a/core/src/data/value/binary_op/decimal.rs +++ b/core/src/data/value/binary_op/decimal.rs @@ -20,6 +20,8 @@ impl PartialEq for Decimal { U8(other) => *self == Decimal::from(*other), U16(other) => *self == Decimal::from(*other), U32(other) => *self == Decimal::from(*other), + U64(other) => *self == Decimal::from(*other), + U128(other) => *self == Decimal::from(*other), F64(other) => Decimal::from_f64_retain(*other) .map(|x| *self == x) .unwrap_or(false), @@ -39,6 +41,8 @@ impl PartialOrd for Decimal { U8(rhs) => self.partial_cmp(&(Decimal::from(rhs))), U16(rhs) => self.partial_cmp(&(Decimal::from(rhs))), U32(rhs) => self.partial_cmp(&(Decimal::from(rhs))), + U64(rhs) => self.partial_cmp(&(Decimal::from(rhs))), + U128(rhs) => self.partial_cmp(&(Decimal::from(rhs))), F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| self.partial_cmp(&x)) .unwrap_or(None), @@ -132,6 +136,30 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U64(rhs) => lhs + .checked_add(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U64(rhs), + operator: NumericBinaryOperator::Add, + } + .into() + }) + .map(Decimal), + U128(rhs) => lhs + .checked_add(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U128(rhs), + operator: NumericBinaryOperator::Add, + } + .into() + }) + .map(Decimal), + + F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs + x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -237,6 +265,29 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U64(rhs) => lhs + .checked_sub(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U64(rhs), + operator: NumericBinaryOperator::Subtract, + } + .into() + }) + .map(Decimal), + U128(rhs) => lhs + .checked_sub(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U128(rhs), + operator: NumericBinaryOperator::Subtract, + } + .into() + }) + .map(Decimal), + F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs - x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -342,6 +393,30 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U64(rhs) => lhs + .checked_mul(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U64(rhs), + operator: NumericBinaryOperator::Multiply, + } + .into() + }) + .map(Decimal), + U128(rhs) => lhs + .checked_mul(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U128(rhs), + operator: NumericBinaryOperator::Multiply, + } + .into() + }) + .map(Decimal), + + F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs * x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -447,6 +522,29 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U64(rhs) => lhs + .checked_div(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U64(rhs), + operator: NumericBinaryOperator::Divide, + } + .into() + }) + .map(Decimal), + U128(rhs) => lhs + .checked_div(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U128(rhs), + operator: NumericBinaryOperator::Divide, + } + .into() + }) + .map(Decimal), + F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs / x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -552,6 +650,29 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), + U64(rhs) => lhs + .checked_rem(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U64(rhs), + operator: NumericBinaryOperator::Modulo, + } + .into() + }) + .map(Decimal), + U128(rhs) => lhs + .checked_rem(Decimal::from(rhs)) + .ok_or_else(|| { + ValueError::BinaryOperationOverflow { + lhs: Decimal(lhs), + rhs: U128(rhs), + operator: NumericBinaryOperator::Modulo, + } + .into() + }) + .map(Decimal), + F64(rhs) => match Decimal::from_f64_retain(rhs) { Some(x) => lhs .checked_rem(x) @@ -666,6 +787,34 @@ mod tests { } .into()) ); + assert_eq!( + Decimal::MAX.try_add(&U32(1)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MAX), + rhs: U32(1), + operator: NumericBinaryOperator::Add, + } + .into()) + ); + assert_eq!( + Decimal::MAX.try_add(&U64(1)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MAX), + rhs: U64(1), + operator: NumericBinaryOperator::Add, + } + .into()) + ); + assert_eq!( + Decimal::MAX.try_add(&U128(1)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MAX), + rhs: U128(1), + operator: NumericBinaryOperator::Add, + } + .into()) + ); + assert_eq!( Decimal::MIN.try_subtract(&I8(1)), Err(ValueError::BinaryOperationOverflow { @@ -720,6 +869,33 @@ mod tests { } .into()) ); + assert_eq!( + Decimal::MIN.try_subtract(&U32(1)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MIN), + rhs: U32(1), + operator: NumericBinaryOperator::Subtract, + } + .into()) + ); + assert_eq!( + Decimal::MIN.try_subtract(&U64(1)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MIN), + rhs: U64(1), + operator: NumericBinaryOperator::Subtract, + } + .into()) + ); + assert_eq!( + Decimal::MIN.try_subtract(&U128(1)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MIN), + rhs: U128(1), + operator: NumericBinaryOperator::Subtract, + } + .into()) + ); assert_eq!( Decimal::MIN.try_subtract(&Decimal(Decimal::ONE)), @@ -786,6 +962,34 @@ mod tests { .into()) ); + assert_eq!( + Decimal::MAX.try_multiply(&U32(2)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MAX), + rhs: U32(2), + operator: NumericBinaryOperator::Multiply, + } + .into()) + ); + assert_eq!( + Decimal::MAX.try_multiply(&U64(2)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MAX), + rhs: U64(2), + operator: NumericBinaryOperator::Multiply, + } + .into()) + ); + assert_eq!( + Decimal::MAX.try_multiply(&U128(2)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(Decimal::MAX), + rhs: U128(2), + operator: NumericBinaryOperator::Multiply, + } + .into()) + ); + assert_eq!( Decimal::MAX.try_multiply(&Decimal(Decimal::TWO)), Err(ValueError::BinaryOperationOverflow { @@ -853,6 +1057,34 @@ mod tests { } .into()) ); + assert_eq!( + base.try_divide(&U32(0)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(base), + rhs: U32(0), + operator: NumericBinaryOperator::Divide, + } + .into()) + ); + assert_eq!( + base.try_divide(&U64(0)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(base), + rhs: U64(0), + operator: NumericBinaryOperator::Divide, + } + .into()) + ); + assert_eq!( + base.try_divide(&U128(0)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(base), + rhs: U128(0), + operator: NumericBinaryOperator::Divide, + } + .into()) + ); + assert_eq!( base.try_divide(&Decimal(Decimal::ZERO)), Err(ValueError::BinaryOperationOverflow { @@ -920,6 +1152,35 @@ mod tests { } .into()) ); + assert_eq!( + base.try_modulo(&U32(0)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(base), + rhs: U32(0), + operator: NumericBinaryOperator::Modulo, + } + .into()) + ); + assert_eq!( + base.try_modulo(&U64(0)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(base), + rhs: U64(0), + operator: NumericBinaryOperator::Modulo, + } + .into()) + ); + assert_eq!( + base.try_modulo(&U128(0)), + Err(ValueError::BinaryOperationOverflow { + lhs: Decimal(base), + rhs: U128(0), + operator: NumericBinaryOperator::Modulo, + } + .into()) + ); + + assert_eq!( base.try_modulo(&Decimal(Decimal::ZERO)), @@ -941,7 +1202,10 @@ mod tests { assert_eq!(base, I64(1)); assert_eq!(base, I128(1)); assert_eq!(base, U8(1)); - assert_eq!(base, U16(1)); + assert_eq!(base, U16(1)); + assert_eq!(base, U32(1)); + assert_eq!(base, U64(1)); + assert_eq!(base, U128(1)); assert_eq!(base, F64(1.0)); assert_eq!(base, Decimal(Decimal::ONE)); @@ -958,6 +1222,9 @@ mod tests { assert_eq!(base.partial_cmp(&I128(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&U8(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&U16(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U32(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U64(1)), Some(Ordering::Equal)); + assert_eq!(base.partial_cmp(&U128(1)), Some(Ordering::Equal)); assert_eq!(base.partial_cmp(&F64(1.0)), Some(Ordering::Equal)); assert_eq!( base.partial_cmp(&Decimal(Decimal::ONE)), @@ -977,6 +1244,9 @@ mod tests { assert_eq!(base.try_add(&I128(1)), Ok(Decimal(Decimal::TWO))); assert_eq!(base.try_add(&U8(1)), Ok(Decimal(Decimal::TWO))); assert_eq!(base.try_add(&U16(1)), Ok(Decimal(Decimal::TWO))); + assert_eq!(base.try_add(&U32(1)), Ok(Decimal(Decimal::TWO))); + assert_eq!(base.try_add(&U64(1)), Ok(Decimal(Decimal::TWO))); + assert_eq!(base.try_add(&U128(1)), Ok(Decimal(Decimal::TWO))); assert_eq!(base.try_add(&F64(1.0)), Ok(Decimal(Decimal::TWO))); assert_eq!( base.try_add(&Decimal(Decimal::ONE)), @@ -1004,6 +1274,9 @@ mod tests { assert_eq!(base.try_subtract(&I128(1)), Ok(Decimal(Decimal::ZERO))); assert_eq!(base.try_subtract(&U8(1)), Ok(Decimal(Decimal::ZERO))); assert_eq!(base.try_subtract(&U16(1)), Ok(Decimal(Decimal::ZERO))); + assert_eq!(base.try_subtract(&U32(1)), Ok(Decimal(Decimal::ZERO))); + assert_eq!(base.try_subtract(&U64(1)), Ok(Decimal(Decimal::ZERO))); + assert_eq!(base.try_subtract(&U128(1)), Ok(Decimal(Decimal::ZERO))); assert_eq!(base.try_subtract(&F64(1.0)), Ok(Decimal(Decimal::ZERO))); assert_eq!( base.try_subtract(&Decimal(Decimal::ONE)), @@ -1031,6 +1304,9 @@ mod tests { assert_eq!(base.try_multiply(&I128(1)), Ok(Decimal(Decimal::ONE))); assert_eq!(base.try_multiply(&U8(1)), Ok(Decimal(Decimal::ONE))); assert_eq!(base.try_multiply(&U16(1)), Ok(Decimal(Decimal::ONE))); + assert_eq!(base.try_multiply(&U32(1)), Ok(Decimal(Decimal::ONE))); + assert_eq!(base.try_multiply(&U64(1)), Ok(Decimal(Decimal::ONE))); + assert_eq!(base.try_multiply(&U128(1)), Ok(Decimal(Decimal::ONE))); assert_eq!(base.try_multiply(&F64(1.0)), Ok(Decimal(Decimal::ONE))); assert_eq!( base.try_multiply(&Decimal(Decimal::ONE)), @@ -1058,6 +1334,9 @@ mod tests { assert_eq!(base.try_divide(&I128(1)), Ok(Decimal(Decimal::ONE))); assert_eq!(base.try_divide(&U8(1)), Ok(Decimal(Decimal::ONE))); assert_eq!(base.try_divide(&U16(1)), Ok(Decimal(Decimal::ONE))); + assert_eq!(base.try_divide(&U32(1)), Ok(Decimal(Decimal::ONE))); + assert_eq!(base.try_divide(&U64(1)), Ok(Decimal(Decimal::ONE))); + assert_eq!(base.try_divide(&U128(1)), Ok(Decimal(Decimal::ONE))); assert_eq!(base.try_divide(&F64(1.0)), Ok(Decimal(Decimal::ONE))); assert_eq!( base.try_divide(&Decimal(Decimal::ONE)), @@ -1085,6 +1364,9 @@ mod tests { assert_eq!(base.try_modulo(&I128(1)), Ok(Decimal(Decimal::ZERO))); assert_eq!(base.try_modulo(&U8(1)), Ok(Decimal(Decimal::ZERO))); assert_eq!(base.try_modulo(&U16(1)), Ok(Decimal(Decimal::ZERO))); + assert_eq!(base.try_modulo(&U32(1)), Ok(Decimal(Decimal::ZERO))); + assert_eq!(base.try_modulo(&U64(1)), Ok(Decimal(Decimal::ZERO))); + assert_eq!(base.try_modulo(&U128(1)), Ok(Decimal(Decimal::ZERO))); assert_eq!(base.try_modulo(&F64(1.0)), Ok(Decimal(Decimal::ZERO))); assert_eq!( base.try_modulo(&Decimal(Decimal::ONE)), From 8d163f38109742cde930d77d8d3c84b46513dcd8 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 13 Jan 2023 23:54:11 +0900 Subject: [PATCH 28/62] style: cargo fmt --- core/src/data/value/binary_op/decimal.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/core/src/data/value/binary_op/decimal.rs b/core/src/data/value/binary_op/decimal.rs index ecf1ec180..0e4482724 100644 --- a/core/src/data/value/binary_op/decimal.rs +++ b/core/src/data/value/binary_op/decimal.rs @@ -158,8 +158,7 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), - - + F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs + x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -416,7 +415,6 @@ impl TryBinaryOperator for Decimal { }) .map(Decimal), - F64(rhs) => Decimal::from_f64_retain(rhs) .map(|x| Ok(Decimal(lhs * x))) .unwrap_or_else(|| Err(ValueError::FloatToDecimalConversionFailure(rhs).into())), @@ -661,7 +659,7 @@ impl TryBinaryOperator for Decimal { .into() }) .map(Decimal), - U128(rhs) => lhs + U128(rhs) => lhs .checked_rem(Decimal::from(rhs)) .ok_or_else(|| { ValueError::BinaryOperationOverflow { @@ -787,7 +785,7 @@ mod tests { } .into()) ); - assert_eq!( + assert_eq!( Decimal::MAX.try_add(&U32(1)), Err(ValueError::BinaryOperationOverflow { lhs: Decimal(Decimal::MAX), @@ -796,7 +794,7 @@ mod tests { } .into()) ); - assert_eq!( + assert_eq!( Decimal::MAX.try_add(&U64(1)), Err(ValueError::BinaryOperationOverflow { lhs: Decimal(Decimal::MAX), @@ -805,7 +803,7 @@ mod tests { } .into()) ); - assert_eq!( + assert_eq!( Decimal::MAX.try_add(&U128(1)), Err(ValueError::BinaryOperationOverflow { lhs: Decimal(Decimal::MAX), @@ -878,7 +876,7 @@ mod tests { } .into()) ); - assert_eq!( + assert_eq!( Decimal::MIN.try_subtract(&U64(1)), Err(ValueError::BinaryOperationOverflow { lhs: Decimal(Decimal::MIN), @@ -887,7 +885,7 @@ mod tests { } .into()) ); - assert_eq!( + assert_eq!( Decimal::MIN.try_subtract(&U128(1)), Err(ValueError::BinaryOperationOverflow { lhs: Decimal(Decimal::MIN), @@ -1180,8 +1178,6 @@ mod tests { .into()) ); - - assert_eq!( base.try_modulo(&Decimal(Decimal::ZERO)), Err(ValueError::BinaryOperationOverflow { @@ -1202,9 +1198,9 @@ mod tests { assert_eq!(base, I64(1)); assert_eq!(base, I128(1)); assert_eq!(base, U8(1)); - assert_eq!(base, U16(1)); + assert_eq!(base, U16(1)); assert_eq!(base, U32(1)); - assert_eq!(base, U64(1)); + assert_eq!(base, U64(1)); assert_eq!(base, U128(1)); assert_eq!(base, F64(1.0)); assert_eq!(base, Decimal(Decimal::ONE)); From 589f32df6bcd6d4a4fc8863521c0a67707761b27 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 12:11:57 +0900 Subject: [PATCH 29/62] test: improve line coverage of `key.rs` --- core/src/data/key.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 6ac242df7..2fe38c299 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -306,6 +306,9 @@ mod tests { assert_eq!(convert("2048"), Ok(Key::I64(2048))); assert_eq!(convert("CAST(11 AS UINT8)"), Ok(Key::U8(11))); assert_eq!(convert("CAST(11 AS UINT16)"), Ok(Key::U16(11))); + assert_eq!(convert("CAST(11 AS UINT32)"), Ok(Key::U32(11))); + assert_eq!(convert("CAST(11 AS UINT64)"), Ok(Key::U64(11))); + assert_eq!(convert("CAST(11 AS UINT128)"), Ok(Key::U128(11))); assert_eq!( convert("CAST(123.45 AS DECIMAL)"), Ok(Key::Decimal(Decimal::from_str("123.45").unwrap())) @@ -475,6 +478,33 @@ mod tests { assert_eq!(cmp(&n1, &n4), Ordering::Less); assert_eq!(cmp(&n3, &n4), Ordering::Equal); + let n1 = U32(0).to_cmp_be_bytes(); + let n2 = U32(3).to_cmp_be_bytes(); + let n3 = U32(20).to_cmp_be_bytes(); + let n4 = U32(20).to_cmp_be_bytes(); + assert_eq!(cmp(&n1, &n2), Ordering::Less); + assert_eq!(cmp(&n3, &n2), Ordering::Greater); + assert_eq!(cmp(&n1, &n4), Ordering::Less); + assert_eq!(cmp(&n3, &n4), Ordering::Equal); + + let n1 = U64(0).to_cmp_be_bytes(); + let n2 = U64(3).to_cmp_be_bytes(); + let n3 = U64(20).to_cmp_be_bytes(); + let n4 = U64(20).to_cmp_be_bytes(); + assert_eq!(cmp(&n1, &n2), Ordering::Less); + assert_eq!(cmp(&n3, &n2), Ordering::Greater); + assert_eq!(cmp(&n1, &n4), Ordering::Less); + assert_eq!(cmp(&n3, &n4), Ordering::Equal); + + let n1 = U128(0).to_cmp_be_bytes(); + let n2 = U128(3).to_cmp_be_bytes(); + let n3 = U128(20).to_cmp_be_bytes(); + let n4 = U128(20).to_cmp_be_bytes(); + assert_eq!(cmp(&n1, &n2), Ordering::Less); + assert_eq!(cmp(&n3, &n2), Ordering::Greater); + assert_eq!(cmp(&n1, &n4), Ordering::Less); + assert_eq!(cmp(&n3, &n4), Ordering::Equal); + let dec = |n| Decimal(rust_decimal::Decimal::from_str(n).unwrap()); let n1 = dec("-1200.345678").to_cmp_be_bytes(); let n2 = dec("-1.01").to_cmp_be_bytes(); From 621c50261bbfb253e5b0904226a903e6c8129903 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 13:26:10 +0900 Subject: [PATCH 30/62] feat: add the missing data type translation in `translate/data_type.rs` --- core/src/translate/data_type.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/translate/data_type.rs b/core/src/translate/data_type.rs index f6ceaffbc..47d87e7ce 100644 --- a/core/src/translate/data_type.rs +++ b/core/src/translate/data_type.rs @@ -32,6 +32,9 @@ pub fn translate_data_type(sql_data_type: &SqlDataType) -> Result { Some("INT128") => Ok(DataType::Int128), Some("UINT8") => Ok(DataType::Uint8), Some("UINT16") => Ok(DataType::Uint16), + Some("UINT32") => Ok(DataType::Uint32), + Some("UINT64") => Ok(DataType::Uint64), + Some("UINT128") => Ok(DataType::Uint128), _ => Err(TranslateError::UnsupportedDataType(sql_data_type.to_string()).into()), } From d6d74418ef791df4521fd85e81af6397b76e2edb Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 13:54:39 +0900 Subject: [PATCH 31/62] feat: Add literals for missing data types --- core/src/data/value/error.rs | 18 ++++++++++++++ core/src/data/value/literal.rs | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/core/src/data/value/error.rs b/core/src/data/value/error.rs index 67f8f0ba6..17b712e67 100644 --- a/core/src/data/value/error.rs +++ b/core/src/data/value/error.rs @@ -98,6 +98,15 @@ pub enum ValueError { #[error("literal cast failed from text to UINT16: {0}")] LiteralCastFromTextToUint16Failed(String), + #[error("literal cast failed from text to UINT32: {0}")] + LiteralCastFromTextToUint32Failed(String), + + #[error("literal cast failed from text to UINT64: {0}")] + LiteralCastFromTextToUint64Failed(String), + + #[error("literal cast failed from text to UINT128: {0}")] + LiteralCastFromTextToUint128Failed(String), + #[error("literal cast failed from text to float: {0}")] LiteralCastFromTextToFloatFailed(String), @@ -122,6 +131,15 @@ pub enum ValueError { #[error("literal cast failed to UINT16: {0}")] LiteralCastToUint16Failed(String), + #[error("literal cast failed to UNIT32: {0}")] + LiteralCastToUint32Failed(String), + + #[error("literal cast failed to UNIT64: {0}")] + LiteralCastToUint64Failed(String), + + #[error("literal cast failed to UNIT128: {0}")] + LiteralCastToUint128Failed(String), + #[error("literal cast failed to time: {0}")] LiteralCastToTimeFailed(String), diff --git a/core/src/data/value/literal.rs b/core/src/data/value/literal.rs index 8e99c1eb0..a5397bd8c 100644 --- a/core/src/data/value/literal.rs +++ b/core/src/data/value/literal.rs @@ -305,6 +305,48 @@ impl Value { Ok(Value::U16(v)) } + (DataType::Uint32, Literal::Text(v)) => v + .parse::() + .map(Value::U32) + .map_err(|_| ValueError::LiteralCastFromTextToUint32Failed(v.to_string()).into()), + (DataType::Uint32, Literal::Number(v)) => match v.to_u32() { + Some(x) => Ok(Value::U32(x)), + None => Err(ValueError::LiteralCastToUint32Failed(v.to_string()).into()), + }, + (DataType::Uint32, Literal::Boolean(v)) => { + let v = u32::from(*v); + + Ok(Value::U32(v)) + } + + (DataType::Uint64, Literal::Text(v)) => v + .parse::() + .map(Value::U64) + .map_err(|_| ValueError::LiteralCastFromTextToUint64Failed(v.to_string()).into()), + (DataType::Uint64, Literal::Number(v)) => match v.to_u64() { + Some(x) => Ok(Value::U64(x)), + None => Err(ValueError::LiteralCastToUint64Failed(v.to_string()).into()), + }, + (DataType::Uint64, Literal::Boolean(v)) => { + let v = u64::from(*v); + + Ok(Value::U64(v)) + } + + (DataType::Uint128, Literal::Text(v)) => v + .parse::() + .map(Value::U128) + .map_err(|_| ValueError::LiteralCastFromTextToUint128Failed(v.to_string()).into()), + (DataType::Uint128, Literal::Number(v)) => match v.to_u128() { + Some(x) => Ok(Value::U128(x)), + None => Err(ValueError::LiteralCastToUint128Failed(v.to_string()).into()), + }, + (DataType::Uint128, Literal::Boolean(v)) => { + let v = u128::from(*v); + + Ok(Value::U128(v)) + } + (DataType::Float, Literal::Text(v)) => v .parse::() .map(Value::F64) @@ -351,6 +393,9 @@ impl Value { | (DataType::Int128, Literal::Null) | (DataType::Uint8, Literal::Null) | (DataType::Uint16, Literal::Null) + | (DataType::Uint32, Literal::Null) + | (DataType::Uint64, Literal::Null) + | (DataType::Uint128, Literal::Null) | (DataType::Float, Literal::Null) | (DataType::Decimal, Literal::Null) | (DataType::Text, Literal::Null) => Ok(Value::Null), From 06efa4a35db5bca1c9a37a0ffdb301e4428d02c5 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 14:15:45 +0900 Subject: [PATCH 32/62] test: improve line coverage of `primitive.rs` --- core/src/data/interval/primitive.rs | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/core/src/data/interval/primitive.rs b/core/src/data/interval/primitive.rs index c72863d4b..26d2d33a0 100644 --- a/core/src/data/interval/primitive.rs +++ b/core/src/data/interval/primitive.rs @@ -483,6 +483,15 @@ mod tests { assert_eq!(Month(2) * 3_u16, Month(6)); assert_eq!(2_u16 * Month(3), Month(6)); + assert_eq!(Month(2) * 3_u32, Month(6)); + assert_eq!(2_u32 * Month(3), Month(6)); + + assert_eq!(Month(2) * 3_u64, Month(6)); + assert_eq!(2_u64 * Month(3), Month(6)); + + assert_eq!(Month(2) * 3_u128, Month(6)); + assert_eq!(2_u128 * Month(3), Month(6)); + assert_eq!(Month(2) * 3.0, Month(6)); assert_eq!(2.0 * Month(3), Month(6)); @@ -507,6 +516,15 @@ mod tests { assert_eq!(Month(6) / 3_u16, Month(2)); assert_eq!(6_u16 / Month(2), Month(3)); + assert_eq!(Month(6) / 3_u32, Month(2)); + assert_eq!(6_u32 / Month(2), Month(3)); + + assert_eq!(Month(6) / 3_u64, Month(2)); + assert_eq!(6_u64 / Month(2), Month(3)); + + assert_eq!(Month(6) / 3_u128, Month(2)); + assert_eq!(6_u128 / Month(2), Month(3)); + assert_eq!(Month(8) / 4.0, Month(2)); assert_eq!(8.0 / Month(4), Month(2)); @@ -531,6 +549,15 @@ mod tests { assert_eq!(Microsecond(2) * 3_u16, Microsecond(6)); assert_eq!(2_u16 * Microsecond(3), Microsecond(6)); + assert_eq!(Microsecond(2) * 3_u32, Microsecond(6)); + assert_eq!(2_u32 * Microsecond(3), Microsecond(6)); + + assert_eq!(Microsecond(2) * 3_u64, Microsecond(6)); + assert_eq!(2_u64 * Microsecond(3), Microsecond(6)); + + assert_eq!(Microsecond(2) * 3_u128, Microsecond(6)); + assert_eq!(2_u128 * Microsecond(3), Microsecond(6)); + assert_eq!(Microsecond(6) / 3_i8, Microsecond(2)); assert_eq!(6_i8 / Microsecond(2), Microsecond(3)); @@ -551,5 +578,14 @@ mod tests { assert_eq!(Microsecond(6) / 3_u16, Microsecond(2)); assert_eq!(6_u16 / Microsecond(2), Microsecond(3)); + + assert_eq!(Microsecond(6) / 3_u32, Microsecond(2)); + assert_eq!(6_u32 / Microsecond(2), Microsecond(3)); + + assert_eq!(Microsecond(6) / 3_u64, Microsecond(2)); + assert_eq!(6_u64 / Microsecond(2), Microsecond(3)); + + assert_eq!(Microsecond(6) / 3_u128, Microsecond(2)); + assert_eq!(6_u128 / Microsecond(2), Microsecond(3)); } } From c0ecf44057bb8ee213d523aceb5b2157ed6b61ca Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 14:52:52 +0900 Subject: [PATCH 33/62] test : improve line coverage --- core/src/data/value/literal.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/core/src/data/value/literal.rs b/core/src/data/value/literal.rs index a5397bd8c..702ea506a 100644 --- a/core/src/data/value/literal.rs +++ b/core/src/data/value/literal.rs @@ -25,6 +25,9 @@ impl PartialEq> for Value { (Value::I128(l), Literal::Number(r)) => r.to_i128().map(|r| *l == r).unwrap_or(false), (Value::U8(l), Literal::Number(r)) => r.to_u8().map(|r| *l == r).unwrap_or(false), (Value::U16(l), Literal::Number(r)) => r.to_u16().map(|r| *l == r).unwrap_or(false), + (Value::U32(l), Literal::Number(r)) => r.to_u32().map(|r| *l == r).unwrap_or(false), + (Value::U64(l), Literal::Number(r)) => r.to_u64().map(|r| *l == r).unwrap_or(false), + (Value::U128(l), Literal::Number(r)) => r.to_u128().map(|r| *l == r).unwrap_or(false), (Value::F64(l), Literal::Number(r)) => r.to_f64().map(|r| *l == r).unwrap_or(false), (Value::Str(l), Literal::Text(r)) => l == r.as_ref(), (Value::Bytea(l), Literal::Bytea(r)) => l == r, @@ -466,12 +469,15 @@ mod tests { assert_eq!(Value::Bool(true), Literal::Boolean(true)); assert_eq!(Value::I8(8), num!("8")); - //assert_eq!(Value::I32(32), num!("32")); // should this work? + assert_eq!(Value::I32(32), num!("32")); // should this work? assert_eq!(Value::I64(64), num!("64")); assert_eq!(Value::I128(128), num!("128")); assert_eq!(Value::F64(7.123), num!("7.123")); assert_eq!(Value::U8(7), num!("7")); assert_eq!(Value::U16(64), num!("64")); + assert_eq!(Value::U32(64), num!("64")); + assert_eq!(Value::U64(64), num!("64")); + assert_eq!(Value::U128(64), num!("64")); assert_eq!(Value::Str("Hello".to_owned()), text!("Hello")); assert_eq!(Value::Bytea(bytea()), Literal::Bytea(bytea())); assert_eq!(Value::Date(date(2021, 11, 20)), text!("2021-11-20")); @@ -578,7 +584,9 @@ mod tests { test!(DataType::Int128, num!("64"), Value::I128(64)); test!(DataType::Uint8, num!("8"), Value::U8(8)); test!(DataType::Uint16, num!("64"), Value::U16(64)); - + test!(DataType::Uint32, num!("64"), Value::U32(64)); + test!(DataType::Uint64, num!("64"), Value::U64(64)); + test!(DataType::Uint128, num!("64"), Value::U128(64)); test!(DataType::Float, num!("123456789"), Value::F64(123456789.0)); test!( DataType::Text, @@ -782,6 +790,21 @@ mod tests { test!(DataType::Uint16, Literal::Boolean(true), Value::U16(1)); test!(DataType::Uint16, Literal::Boolean(false), Value::U16(0)); + test!(DataType::Uint32, text!("127"), Value::U32(127)); + test!(DataType::Uint32, num!("125"), Value::U32(125)); + test!(DataType::Uint32, Literal::Boolean(true), Value::U32(1)); + test!(DataType::Uint32, Literal::Boolean(false), Value::U32(0)); + + test!(DataType::Uint64, text!("127"), Value::U64(127)); + test!(DataType::Uint64, num!("125"), Value::U64(125)); + test!(DataType::Uint64, Literal::Boolean(true), Value::U64(1)); + test!(DataType::Uint64, Literal::Boolean(false), Value::U64(0)); + + test!(DataType::Uint128, text!("127"), Value::U128(127)); + test!(DataType::Uint128, num!("125"), Value::U128(125)); + test!(DataType::Uint128, Literal::Boolean(true), Value::U128(1)); + test!(DataType::Uint128, Literal::Boolean(false), Value::U128(0)); + test!(DataType::Float, text!("12345.6789"), Value::F64(12345.6789)); test!(DataType::Float, num!("123456.789"), Value::F64(123456.789)); test!(DataType::Float, Literal::Boolean(true), Value::F64(1.0)); @@ -817,6 +840,9 @@ mod tests { test_null!(DataType::Int8, Literal::Null); test_null!(DataType::Uint8, Literal::Null); test_null!(DataType::Uint16, Literal::Null); + test_null!(DataType::Uint32, Literal::Null); + test_null!(DataType::Uint64, Literal::Null); + test_null!(DataType::Uint128, Literal::Null); test_null!(DataType::Float, Literal::Null); test_null!(DataType::Text, Literal::Null); test!( From cf4a4c8d13bb1228b06bcdc7f5e67f924804ab58 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 15:02:15 +0900 Subject: [PATCH 34/62] fix : resolve `try_from_literal` error --- core/src/data/value/literal.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/src/data/value/literal.rs b/core/src/data/value/literal.rs index 702ea506a..e1984d730 100644 --- a/core/src/data/value/literal.rs +++ b/core/src/data/value/literal.rs @@ -73,6 +73,16 @@ impl PartialOrd> for Value { (Value::U16(l), Literal::Number(r)) => { r.to_u16().map(|r| l.partial_cmp(&r)).unwrap_or(None) } + (Value::U32(l), Literal::Number(r)) => { + r.to_u32().map(|r| l.partial_cmp(&r)).unwrap_or(None) + } + (Value::U64(l), Literal::Number(r)) => { + r.to_u64().map(|r| l.partial_cmp(&r)).unwrap_or(None) + } + (Value::U128(l), Literal::Number(r)) => { + r.to_u128().map(|r| l.partial_cmp(&r)).unwrap_or(None) + } + (Value::F64(l), Literal::Number(r)) => { r.to_f64().map(|r| l.partial_cmp(&r)).unwrap_or(None) } @@ -158,6 +168,18 @@ impl Value { .to_u16() .map(Value::U16) .ok_or_else(|| ValueError::FailedToParseNumber.into()), + (DataType::Uint32, Literal::Number(v)) => v + .to_u32() + .map(Value::U32) + .ok_or_else(|| ValueError::FailedToParseNumber.into()), + (DataType::Uint64, Literal::Number(v)) => v + .to_u64() + .map(Value::U64) + .ok_or_else(|| ValueError::FailedToParseNumber.into()), + (DataType::Uint128, Literal::Number(v)) => v + .to_u128() + .map(Value::U128) + .ok_or_else(|| ValueError::FailedToParseNumber.into()), (DataType::Float, Literal::Number(v)) => v .to_f64() .map(Value::F64) From 288c453558711d2c98ccd8298a2b64ac648fb20d Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 15:54:52 +0900 Subject: [PATCH 35/62] test: improve line coverage of `mod.rs` --- core/src/data/value/mod.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 0352fb2ce..da146d095 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -883,6 +883,14 @@ mod tests { } assert!(U8(0).is_zero()); assert!(!U8(1).is_zero()); + assert!(U16(0).is_zero()); + assert!(!U16(1).is_zero()); + assert!(U32(0).is_zero()); + assert!(!U32(1).is_zero()); + assert!(U64(0).is_zero()); + assert!(!U64(1).is_zero()); + assert!(U128(0).is_zero()); + assert!(!U128(1).is_zero()); } #[test] @@ -974,6 +982,36 @@ mod tests { test!(add U16(1), U8(2) => U16(3)); test!(add U16(1), F64(2.0) => F64(3.0)); + test!(add U32(1), I8(2) => U32(3)); + test!(add U32(1), I16(2) => U32(3)); + test!(add U32(1), I32(2) => U32(3)); + test!(add U32(1), I64(2) => U32(3)); + test!(add U32(1), I128(2) => U32(3)); + test!(add U32(1), U8(2) => U32(3)); + test!(add U32(1), U16(2) => U32(3)); + test!(add U32(1), U32(2) => U32(3)); + test!(add U32(1), F64(2.0) => F64(3.0)); + + test!(add U64(1), I8(2) => U64(3)); + test!(add U64(1), I16(2) => U64(3)); + test!(add U64(1), I32(2) => U64(3)); + test!(add U64(1), I64(2) => U64(3)); + test!(add U64(1), I128(2) => U64(3)); + test!(add U64(1), U8(2) => U64(3)); + test!(add U64(1), U16(2) => U64(3)); + test!(add U64(1), U32(2) => U64(3)); + test!(add U64(1), F64(2.0) => F64(3.0)); + + test!(add U128(1), I8(2) => U128(3)); + test!(add U128(1), I16(2) => U128(3)); + test!(add U128(1), I32(2) => U128(3)); + test!(add U128(1), I64(2) => U128(3)); + test!(add U128(1), I128(2) => U128(3)); + test!(add U128(1), U8(2) => U128(3)); + test!(add U128(1), U16(2) => U128(3)); + test!(add U128(1), U32(2) => U128(3)); + test!(add U128(1), F64(2.0) => F64(3.0)); + test!(add F64(1.0), F64(2.0) => F64(3.0)); test!(add F64(1.0), I8(2) => F64(3.0)); test!(add F64(1.0), I32(2) => F64(3.0)); From 0cb6a7b415c3f2ddec6850ab9de9a2ab15498281 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 16:26:50 +0900 Subject: [PATCH 36/62] test: improve line coverage of `mod.rs` --- core/src/data/value/mod.rs | 95 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index da146d095..91b7558e3 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1096,6 +1096,30 @@ mod tests { test!(subtract U16(3), U8(2) => U16(1)); test!(subtract U16(3), F64(2.0) => F64(1.0)); + test!(subtract U32(3), I8(2) => U32(1)); + test!(subtract U32(3), I16(2) => U32(1)); + test!(subtract U32(3), I32(2) => U32(1)); + test!(subtract U32(3), I64(2) => U32(1)); + test!(subtract U32(3), I128(2) => U32(1)); + test!(subtract U32(3), U8(2) => U32(1)); + test!(subtract U32(3), F64(2.0) => F64(1.0)); + + test!(subtract U64(3), I8(2) => U64(1)); + test!(subtract U64(3), I16(2) => U64(1)); + test!(subtract U64(3), I32(2) => U64(1)); + test!(subtract U64(3), I64(2) => U64(1)); + test!(subtract U64(3), I128(2) => U64(1)); + test!(subtract U64(3), U8(2) => U64(1)); + test!(subtract U64(3), F64(2.0) => F64(1.0)); + + test!(subtract U128(3), I8(2) => U128(1)); + test!(subtract U128(3), I16(2) => U128(1)); + test!(subtract U128(3), I32(2) => U128(1)); + test!(subtract U128(3), I64(2) => U128(1)); + test!(subtract U128(3), I128(2) => U128(1)); + test!(subtract U128(3), U8(2) => U128(1)); + test!(subtract U128(3), F64(2.0) => F64(1.0)); + test!(subtract I8(3), F64(2.0) => F64(1.0)); test!(subtract I32(3), F64(2.0) => F64(1.0)); test!(subtract I64(3), F64(2.0) => F64(1.0)); @@ -1205,6 +1229,22 @@ mod tests { test!(multiply U16(3), U8(2) => U16(6)); test!(multiply U16(3), F64(2.0) => F64(6.0)); + test!(multiply U32(3), I8(2) => U32(6)); + test!(multiply U32(3), I16(2) => U32(6)); + test!(multiply U32(3), I32(2) => U32(6)); + test!(multiply U32(3), I64(2) => U32(6)); + test!(multiply U32(3), I128(2) => U32(6)); + test!(multiply U32(3), U8(2) => U32(6)); + test!(multiply U32(3), F64(2.0) => F64(6.0)); + + test!(multiply U64(3), I8(2) => U64(6)); + test!(multiply U64(3), I16(2) => U64(6)); + test!(multiply U64(3), I32(2) => U64(6)); + test!(multiply U64(3), I64(2) => U64(6)); + test!(multiply U64(3), I128(2) => U64(6)); + test!(multiply U64(3), U8(2) => U64(6)); + test!(multiply U64(3), F64(2.0) => F64(6.0)); + test!(multiply F64(3.0), F64(2.0) => F64(6.0)); test!(multiply F64(3.0), I8(2) => F64(6.0)); test!(multiply F64(3.0), I32(2) => F64(6.0)); @@ -1280,6 +1320,22 @@ mod tests { test!(divide U16(6), U8(2) => U16(3)); test!(divide U16(6), F64(2.0) => F64(3.0)); + test!(divide U32(6), I8(2) => U32(3)); + test!(divide U32(6), I16(2) => U32(3)); + test!(divide U32(6), I32(2) => U32(3)); + test!(divide U32(6), I64(2) => U32(3)); + test!(divide U32(6), I128(2) => U32(3)); + test!(divide U32(6), U8(2) => U32(3)); + test!(divide U32(6), F64(2.0) => F64(3.0)); + + test!(divide U64(6), I8(2) => U64(3)); + test!(divide U64(6), I16(2) => U64(3)); + test!(divide U64(6), I32(2) => U64(3)); + test!(divide U64(6), I64(2) => U64(3)); + test!(divide U64(6), I128(2) => U64(3)); + test!(divide U64(6), U8(2) => U64(3)); + test!(divide U64(6), F64(2.0) => F64(3.0)); + test!(divide I8(6), F64(2.0) => F64(3.0)); test!(divide I32(6), F64(2.0) => F64(3.0)); test!(divide I64(6), F64(2.0) => F64(3.0)); @@ -1363,6 +1419,9 @@ mod tests { null_test!(add I128(1), Null); null_test!(add U8(1), Null); null_test!(add U16(1), Null); + null_test!(add U32(1), Null); + null_test!(add U64(1), Null); + null_test!(add U128(1), Null); null_test!(add F64(1.0), Null); null_test!(add decimal(1), Null); null_test!(add date(), Null); @@ -1376,6 +1435,9 @@ mod tests { null_test!(subtract I128(1), Null); null_test!(subtract U8(1), Null); null_test!(subtract U16(1), Null); + null_test!(subtract U32(1), Null); + null_test!(subtract U64(1), Null); + null_test!(subtract U128(1), Null); null_test!(subtract F64(1.0), Null); null_test!(subtract decimal(1), Null); null_test!(subtract date(), Null); @@ -1389,6 +1451,9 @@ mod tests { null_test!(multiply I128(1), Null); null_test!(multiply U8(1), Null); null_test!(multiply U16(1), Null); + null_test!(multiply U32(1), Null); + null_test!(multiply U64(1), Null); + null_test!(multiply U128(1), Null); null_test!(multiply F64(1.0), Null); null_test!(multiply decimal(1), Null); null_test!(multiply mon!(1), Null); @@ -1399,6 +1464,9 @@ mod tests { null_test!(divide I128(1), Null); null_test!(divide U8(1), Null); null_test!(divide U16(1), Null); + null_test!(divide U32(1), Null); + null_test!(divide U64(1), Null); + null_test!(divide U128(1), Null); null_test!(divide F64(1.0), Null); null_test!(divide decimal(1), Null); null_test!(divide mon!(1), Null); @@ -1409,6 +1477,9 @@ mod tests { null_test!(modulo I128(1), Null); null_test!(modulo U8(1), Null); null_test!(modulo U16(1), Null); + null_test!(modulo U32(1), Null); + null_test!(modulo U64(1), Null); + null_test!(modulo U128(1), Null); null_test!(modulo F64(1.0), Null); null_test!(modulo decimal(1), Null); @@ -1419,6 +1490,9 @@ mod tests { null_test!(add Null, I128(1)); null_test!(add Null, U8(1)); null_test!(add Null, U16(1)); + null_test!(add Null, U32(1)); + null_test!(add Null, U64(1)); + null_test!(add Null, U128(1)); null_test!(add Null, F64(1.0)); null_test!(add Null, decimal(1)); null_test!(add Null, mon!(1)); @@ -1431,6 +1505,9 @@ mod tests { null_test!(subtract Null, I128(1)); null_test!(subtract Null, U8(1)); null_test!(subtract Null, U16(1)); + null_test!(subtract Null, U32(1)); + null_test!(subtract Null, U64(1)); + null_test!(subtract Null, U128(1)); null_test!(subtract Null, F64(1.0)); null_test!(subtract Null, decimal(1)); null_test!(subtract Null, date()); @@ -1444,6 +1521,9 @@ mod tests { null_test!(multiply Null, I128(1)); null_test!(multiply Null, U8(1)); null_test!(multiply Null, U16(1)); + null_test!(multiply Null, U32(1)); + null_test!(multiply Null, U64(1)); + null_test!(multiply Null, U128(1)); null_test!(multiply Null, F64(1.0)); null_test!(multiply Null, decimal(1)); null_test!(divide Null, I8(1)); @@ -1453,6 +1533,9 @@ mod tests { null_test!(divide Null, I128(1)); null_test!(divide Null, U8(1)); null_test!(divide Null, U16(1)); + null_test!(divide Null, U32(1)); + null_test!(divide Null, U64(1)); + null_test!(divide Null, U128(1)); null_test!(divide Null, F64(1.0)); null_test!(divide Null, decimal(1)); null_test!(modulo Null, I8(1)); @@ -1461,6 +1544,9 @@ mod tests { null_test!(modulo Null, I128(1)); null_test!(modulo Null, U8(1)); null_test!(modulo Null, U16(1)); + null_test!(modulo Null, U32(1)); + null_test!(modulo Null, U64(1)); + null_test!(modulo Null, U128(1)); null_test!(modulo Null, F64(1.0)); null_test!(modulo Null, decimal(1)); @@ -1504,6 +1590,9 @@ mod tests { cast!(I128(1) => Int128 , I128(1)); cast!(U8(1) => Uint8 , U8(1)); cast!(U16(1) => Uint16 , U16(1)); + cast!(U32(1) => Uint32 , U32(1)); + cast!(U64(1) => Uint64 , U64(1)); + cast!(U128(1) => Uint128 , U128(1)); cast!(F64(1.0) => Float , F64(1.0)); cast!(Value::Uuid(123) => Uuid , Value::Uuid(123)); @@ -1523,6 +1612,12 @@ mod tests { cast!(U8(0) => Boolean, Bool(false)); cast!(U16(1) => Boolean, Bool(true)); cast!(U16(0) => Boolean, Bool(false)); + cast!(U32(1) => Boolean, Bool(true)); + cast!(U32(1) => Boolean, Bool(true)); + cast!(U64(1) => Boolean, Bool(true)); + cast!(U64(0) => Boolean, Bool(false)); + cast!(U128(0) => Boolean, Bool(false)); + cast!(U128(0) => Boolean, Bool(false)); cast!(F64(1.0) => Boolean, Bool(true)); cast!(F64(0.0) => Boolean, Bool(false)); cast!(Null => Boolean, Null); From d78bc0262667a8deb8d0bf16fc8099a0b31d2a35 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 17:38:22 +0900 Subject: [PATCH 37/62] test: add missing tests --- core/src/data/value/mod.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 91b7558e3..24a84822e 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1119,7 +1119,7 @@ mod tests { test!(subtract U128(3), I128(2) => U128(1)); test!(subtract U128(3), U8(2) => U128(1)); test!(subtract U128(3), F64(2.0) => F64(1.0)); - + test!(subtract I8(3), F64(2.0) => F64(1.0)); test!(subtract I32(3), F64(2.0) => F64(1.0)); test!(subtract I64(3), F64(2.0) => F64(1.0)); @@ -1319,7 +1319,7 @@ mod tests { test!(divide U16(6), I128(2) => U16(3)); test!(divide U16(6), U8(2) => U16(3)); test!(divide U16(6), F64(2.0) => F64(3.0)); - + test!(divide U32(6), I8(2) => U32(3)); test!(divide U32(6), I16(2) => U32(3)); test!(divide U32(6), I32(2) => U32(3)); @@ -1336,6 +1336,14 @@ mod tests { test!(divide U64(6), U8(2) => U64(3)); test!(divide U64(6), F64(2.0) => F64(3.0)); + test!(divide U128(6), I8(2) => U128(3)); + test!(divide U128(6), I16(2) => U128(3)); + test!(divide U128(6), I32(2) => U128(3)); + test!(divide U128(6), I64(2) => U128(3)); + test!(divide U128(6), I128(2) => U128(3)); + test!(divide U128(6), U8(2) => U128(3)); + test!(divide U128(6), F64(2.0) => F64(3.0)); + test!(divide I8(6), F64(2.0) => F64(3.0)); test!(divide I32(6), F64(2.0) => F64(3.0)); test!(divide I64(6), F64(2.0) => F64(3.0)); @@ -1355,6 +1363,10 @@ mod tests { test!(divide mon!(6), I64(2) => mon!(3)); test!(divide mon!(6), I128(2) => mon!(3)); test!(divide mon!(6), U8(2) => mon!(3)); + test!(divide mon!(6), U16(2) => mon!(3)); + test!(divide mon!(6), U32(2) => mon!(3)); + test!(divide mon!(6), U64(2) => mon!(3)); + test!(divide mon!(6), U128(2) => mon!(3)); test!(divide mon!(6), F64(2.0) => mon!(3)); test!(modulo I8(6), I8(4) => I8(2)); @@ -1653,6 +1665,7 @@ mod tests { cast!(Str("11".to_owned()) => Uint8, U8(11)); cast!(Null => Uint8, Null); + // Float cast!(Bool(true) => Float, F64(1.0)); cast!(Bool(false) => Float, F64(0.0)); From 60dcdd3b4f929f4e4f275333b3343f57e5ca7b87 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 14 Jan 2023 17:38:46 +0900 Subject: [PATCH 38/62] style: cargo fmt --- core/src/data/value/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 24a84822e..914631f39 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1119,7 +1119,7 @@ mod tests { test!(subtract U128(3), I128(2) => U128(1)); test!(subtract U128(3), U8(2) => U128(1)); test!(subtract U128(3), F64(2.0) => F64(1.0)); - + test!(subtract I8(3), F64(2.0) => F64(1.0)); test!(subtract I32(3), F64(2.0) => F64(1.0)); test!(subtract I64(3), F64(2.0) => F64(1.0)); @@ -1319,7 +1319,7 @@ mod tests { test!(divide U16(6), I128(2) => U16(3)); test!(divide U16(6), U8(2) => U16(3)); test!(divide U16(6), F64(2.0) => F64(3.0)); - + test!(divide U32(6), I8(2) => U32(3)); test!(divide U32(6), I16(2) => U32(3)); test!(divide U32(6), I32(2) => U32(3)); @@ -1665,7 +1665,6 @@ mod tests { cast!(Str("11".to_owned()) => Uint8, U8(11)); cast!(Null => Uint8, Null); - // Float cast!(Bool(true) => Float, F64(1.0)); cast!(Bool(false) => Float, F64(0.0)); From 18aa5797a103c94f015ca34d7c2da5b16d12bb4b Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 10 Feb 2023 16:32:44 +0900 Subject: [PATCH 39/62] merge main into feature branch --- .github/workflows/rust.yml | 2 + Cargo.toml | 7 + cli/Cargo.toml | 10 +- cli/src/lib.rs | 10 +- cli/tests/dump.rs | 6 +- core/Cargo.toml | 11 +- core/src/ast/data_type.rs | 1 + core/src/ast/function.rs | 18 + core/src/ast/mod.rs | 119 ++-- core/src/ast_builder/create_table.rs | 29 +- core/src/ast_builder/expr/function.rs | 63 ++- core/src/ast_builder/mod.rs | 5 +- core/src/data/bigdecimal_ext.rs | 10 + core/src/data/key.rs | 125 ++++- core/src/data/literal.rs | 41 +- core/src/data/schema.rs | 11 +- core/src/data/string_ext.rs | 2 +- core/src/data/value/convert.rs | 151 ++++- core/src/data/value/error.rs | 6 + core/src/data/value/expr.rs | 1 + core/src/data/value/json.rs | 6 + core/src/data/value/literal.rs | 110 +++- core/src/data/value/mod.rs | 55 +- core/src/data/value/string.rs | 65 +++ core/src/executor/aggregate/mod.rs | 16 +- core/src/executor/aggregate/state.rs | 12 +- core/src/executor/alter/index.rs | 52 +- core/src/executor/alter/table.rs | 262 ++++----- core/src/executor/evaluate/error.rs | 12 + core/src/executor/evaluate/evaluated.rs | 527 +++++++++++++++++- core/src/executor/evaluate/expr.rs | 2 +- core/src/executor/evaluate/function.rs | 94 +--- core/src/executor/evaluate/mod.rs | 24 +- core/src/executor/evaluate/stateless.rs | 9 +- core/src/executor/execute.rs | 272 ++++----- core/src/executor/fetch.rs | 86 ++- core/src/executor/filter.rs | 12 +- core/src/executor/insert.rs | 46 +- core/src/executor/join.rs | 20 +- core/src/executor/select/mod.rs | 10 +- core/src/executor/select/project.rs | 8 +- core/src/executor/sort.rs | 67 ++- core/src/executor/update.rs | 12 +- core/src/executor/validate.rs | 4 +- core/src/glue.rs | 25 +- core/src/lib.rs | 1 + core/src/plan/evaluable.rs | 8 +- core/src/plan/expr/function.rs | 7 +- core/src/plan/mock.rs | 108 ++-- core/src/plan/mod.rs | 6 +- core/src/plan/schema.rs | 85 +-- core/src/plan/validate.rs | 208 +++++-- core/src/result.rs | 18 - core/src/store/alter_table.rs | 27 +- core/src/store/index.rs | 17 +- core/src/store/mod.rs | 19 +- core/src/store/transaction.rs | 30 +- core/src/translate/data_type.rs | 1 + core/src/translate/function.rs | 9 + core/src/translate/mod.rs | 28 +- pkg/javascript/examples/nodejs/main.js | 6 +- pkg/javascript/examples/web/module/index.html | 9 +- pkg/javascript/examples/web/rollup/main.js | 8 +- .../examples/web/rollup/package.json | 4 +- pkg/javascript/examples/web/webpack/main.js | 6 +- .../examples/web/webpack/package.json | 4 +- pkg/javascript/web/Cargo.toml | 6 +- pkg/javascript/web/package.json | 2 +- pkg/javascript/web/src/lib.rs | 102 +++- pkg/javascript/web/tests/composite_storage.rs | 31 ++ pkg/javascript/web/tests/error.rs | 60 ++ .../web/tests/join_multiple_storages.rs | 92 +++ pkg/javascript/web/tests/memory_storage.rs | 29 - pkg/rust/Cargo.toml | 10 +- pkg/rust/examples/api_usage.rs | 24 +- storages/composite-storage/Cargo.toml | 28 + storages/composite-storage/src/lib.rs | 94 ++++ storages/composite-storage/src/store.rs | 52 ++ storages/composite-storage/src/store_mut.rs | 64 +++ storages/composite-storage/src/transaction.rs | 43 ++ storages/composite-storage/tests/basic.rs | 75 +++ .../tests/composite_storage.rs | 27 + storages/composite-storage/tests/error.rs | 114 ++++ .../tests/memory_and_sled.rs | 63 +++ storages/idb-storage/Cargo.toml | 35 ++ storages/idb-storage/README.md | 6 + storages/idb-storage/src/convert.rs | 46 ++ storages/idb-storage/src/error.rs | 14 + storages/idb-storage/src/lib.rs | 434 +++++++++++++++ storages/idb-storage/tests/convert.rs | 74 +++ storages/idb-storage/tests/idb_storage.rs | 31 ++ storages/memory-storage/Cargo.toml | 10 +- storages/memory-storage/src/alter_table.rs | 52 +- storages/memory-storage/src/index.rs | 18 +- storages/memory-storage/src/lib.rs | 64 +-- storages/memory-storage/src/transaction.rs | 25 +- .../memory-storage/tests/memory_storage.rs | 9 +- storages/shared-memory-storage/Cargo.toml | 10 +- .../shared-memory-storage/src/alter_table.rs | 51 +- storages/shared-memory-storage/src/index.rs | 18 +- storages/shared-memory-storage/src/lib.rs | 32 +- .../shared-memory-storage/src/transaction.rs | 25 +- storages/sled-storage/Cargo.toml | 10 +- storages/sled-storage/src/alter_table.rs | 68 ++- storages/sled-storage/src/index_mut.rs | 42 +- storages/sled-storage/src/lib.rs | 9 - storages/sled-storage/src/store_mut.rs | 82 ++- storages/sled-storage/src/transaction.rs | 152 ++--- .../sled-storage/tests/export_and_import.rs | 6 +- .../sled-storage/tests/sled_transaction.rs | 20 +- storages/web-storage/src/lib.rs | 43 +- test-suite/Cargo.toml | 10 +- test-suite/src/basic.rs | 11 +- test-suite/src/column_alias.rs | 130 +++++ test-suite/src/concat.rs | 36 ++ test-suite/src/data_type/inet.rs | 62 +++ test-suite/src/data_type/int64.rs | 6 +- test-suite/src/data_type/mod.rs | 1 + test-suite/src/function/cast.rs | 4 + test-suite/src/function/ltrim_rtrim.rs | 71 +++ test-suite/src/function/mod.rs | 1 + test-suite/src/function/rand.rs | 59 ++ test-suite/src/function/substr.rs | 125 ++++- test-suite/src/function/trim.rs | 26 +- test-suite/src/join.rs | 23 +- test-suite/src/lib.rs | 4 + test-suite/src/like_ilike.rs | 19 +- test-suite/src/nullable.rs | 19 +- test-suite/src/primary_key.rs | 9 +- test-suite/src/schemaless/error.rs | 7 + test-suite/src/tester/mod.rs | 6 +- test-suite/src/update.rs | 4 + test-suite/src/values.rs | 24 +- utils/Cargo.toml | 10 +- 134 files changed, 4535 insertions(+), 1479 deletions(-) create mode 100644 core/src/data/value/string.rs create mode 100644 pkg/javascript/web/tests/composite_storage.rs create mode 100644 pkg/javascript/web/tests/error.rs create mode 100644 pkg/javascript/web/tests/join_multiple_storages.rs delete mode 100644 pkg/javascript/web/tests/memory_storage.rs create mode 100644 storages/composite-storage/Cargo.toml create mode 100644 storages/composite-storage/src/lib.rs create mode 100644 storages/composite-storage/src/store.rs create mode 100644 storages/composite-storage/src/store_mut.rs create mode 100644 storages/composite-storage/src/transaction.rs create mode 100644 storages/composite-storage/tests/basic.rs create mode 100644 storages/composite-storage/tests/composite_storage.rs create mode 100644 storages/composite-storage/tests/error.rs create mode 100644 storages/composite-storage/tests/memory_and_sled.rs create mode 100644 storages/idb-storage/Cargo.toml create mode 100644 storages/idb-storage/README.md create mode 100644 storages/idb-storage/src/convert.rs create mode 100644 storages/idb-storage/src/error.rs create mode 100644 storages/idb-storage/src/lib.rs create mode 100644 storages/idb-storage/tests/convert.rs create mode 100644 storages/idb-storage/tests/idb_storage.rs create mode 100644 test-suite/src/column_alias.rs create mode 100644 test-suite/src/data_type/inet.rs create mode 100644 test-suite/src/function/rand.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9826889f9..aa9186cef 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -120,4 +120,6 @@ jobs: cd ../../../ cd storages/web-storage wasm-pack test --headless --firefox + cd ../idb-storage + WASM_BINDGEN_TEST_TIMEOUT=60 wasm-pack test --headless --firefox cd ../../ diff --git a/Cargo.toml b/Cargo.toml index 775b412a0..a3ec6ceb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,10 @@ default-members = [ # enable this only for gluesql-js build # [profile.release] # opt-level = "s" + +[workspace.package] +edition = "2021" +description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" +license = "Apache-2.0" +repository = "https://github.com/gluesql/gluesql" +documentation = "https://docs.rs/gluesql/" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index be94a1dce..118d927f9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "gluesql-cli" version = "0.13.1" -edition = "2021" authors = ["Taehoon Moon "] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] gluesql-core = { path = "../core", version = "0.13.1", features = ["alter-table"] } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e313336d5..1616f5ce5 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -44,8 +44,8 @@ pub fn run() -> Result<()> { let path = path.as_path().to_str().expect("wrong path"); if let Some(dump_path) = args.dump { - let storage = SledStorage::new(path).expect("failed to load sled-storage"); - dump_database(storage, dump_path)?; + let mut storage = SledStorage::new(path).expect("failed to load sled-storage"); + dump_database(&mut storage, dump_path)?; return Ok::<_, Error>(()); } @@ -78,11 +78,11 @@ pub fn run() -> Result<()> { Ok(()) } -pub fn dump_database(storage: SledStorage, dump_path: PathBuf) -> Result { +pub fn dump_database(storage: &mut SledStorage, dump_path: PathBuf) -> Result<()> { let file = File::create(dump_path)?; block_on(async { - let (storage, _) = storage.begin(true).await.map_err(|(_, error)| error)?; + storage.begin(true).await?; let schemas = storage.fetch_all_schemas().await?; for schema in schemas { writeln!(&file, "{}", schema.clone().to_ddl())?; @@ -128,6 +128,6 @@ pub fn dump_database(storage: SledStorage, dump_path: PathBuf) -> Result"] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] utils = { package = "gluesql-utils", path = "../utils", version = "0.13.0" } @@ -29,6 +29,7 @@ thiserror = "1.0" strum_macros = "0.24" bigdecimal = { version = "0.3", features = ["serde", "string-only"] } hex = "0.4" +rand = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies.uuid] version = "1" diff --git a/core/src/ast/data_type.rs b/core/src/ast/data_type.rs index 235e4971f..54ce7060c 100644 --- a/core/src/ast/data_type.rs +++ b/core/src/ast/data_type.rs @@ -20,6 +20,7 @@ pub enum DataType { Float, Text, Bytea, + Inet, Date, Timestamp, Time, diff --git a/core/src/ast/function.rs b/core/src/ast/function.rs index 63d3a073c..04803d277 100644 --- a/core/src/ast/function.rs +++ b/core/src/ast/function.rs @@ -46,6 +46,7 @@ pub enum Function { expr: Expr, then: Expr, }, + Rand(Option), Round(Expr), Floor(Expr), Trim { @@ -195,6 +196,10 @@ impl ToSql for Function { Function::IfNull { expr, then } => { format!("IFNULL({}, {})", expr.to_sql(), then.to_sql()) } + Function::Rand(e) => match e { + Some(v) => format!("RAND({})", v.to_sql()), + None => "RAND()".to_owned(), + }, Function::Round(e) => format!("ROUND({})", e.to_sql()), Function::Floor(e) => format!("FLOOR({})", e.to_sql()), Function::Trim { @@ -496,6 +501,19 @@ mod tests { .to_sql() ); + assert_eq!( + "RAND()", + &Expr::Function(Box::new(Function::Rand(None))).to_sql() + ); + + assert_eq!( + "RAND(num)", + &Expr::Function(Box::new(Function::Rand(Some(Expr::Identifier( + "num".to_owned() + ))))) + .to_sql() + ); + assert_eq!( "ROUND(num)", &Expr::Function(Box::new(Function::Round(Expr::Identifier( diff --git a/core/src/ast/mod.rs b/core/src/ast/mod.rs index a212a46bc..a05d77470 100644 --- a/core/src/ast/mod.rs +++ b/core/src/ast/mod.rs @@ -60,8 +60,9 @@ pub enum Statement { /// Table name name: String, /// Optional schema - columns: Vec, + columns: Option>, source: Option>, + engine: Option, }, /// ALTER TABLE #[cfg(feature = "alter-table")] @@ -167,27 +168,42 @@ impl ToSql for Statement { name, columns, source, - } => match source { - Some(query) => match if_not_exists { - true => format!("CREATE TABLE IF NOT EXISTS {name} AS {};", query.to_sql()), - false => format!("CREATE TABLE {name} AS {};", query.to_sql()), - }, - None if columns.is_empty() => match if_not_exists { - true => format!("CREATE TABLE IF NOT EXISTS {name};"), - false => format!("CREATE TABLE {name};"), - }, - None => { - let columns = columns - .iter() - .map(ToSql::to_sql) - .collect::>() - .join(", "); - match if_not_exists { - true => format!("CREATE TABLE IF NOT EXISTS {name} ({columns});"), - false => format!("CREATE TABLE {name} ({columns});"), + engine, + } => { + let if_not_exists = if_not_exists.then_some("IF NOT EXISTS"); + let body = match source { + Some(query) => Some(format!("AS {}", query.to_sql())), + None if columns.is_none() => None, + None => { + let columns = columns + .as_ref() + .map(|columns| { + columns + .iter() + .map(ToSql::to_sql) + .collect::>() + .join(", ") + }) + .unwrap_or_else(|| "".to_owned()); + + Some(format!("({columns})")) } - } - }, + }; + let engine = engine.as_ref().map(|engine| format!("ENGINE = {engine}")); + let sql = vec![ + Some("CREATE TABLE"), + if_not_exists, + Some(name), + body.as_deref(), + engine.as_deref(), + ] + .into_iter() + .flatten() + .collect::>() + .join(" "); + + format!("{sql};") + } #[cfg(feature = "alter-table")] Statement::AlterTable { name, operation } => { format!("ALTER TABLE {name} {};", operation.to_sql()) @@ -359,8 +375,9 @@ mod tests { Statement::CreateTable { if_not_exists: true, name: "Foo".into(), - columns: Vec::new(), + columns: None, source: None, + engine: None, } .to_sql() ); @@ -370,8 +387,9 @@ mod tests { Statement::CreateTable { if_not_exists: false, name: "Foo".into(), - columns: Vec::new(), + columns: None, source: None, + engine: None, } .to_sql() ); @@ -381,14 +399,15 @@ mod tests { Statement::CreateTable { if_not_exists: true, name: "Foo".into(), - columns: vec![ColumnDef { + columns: Some(vec![ColumnDef { name: "id".to_owned(), data_type: DataType::Boolean, nullable: false, default: None, unique: None, - },], + },]), source: None, + engine: None, } .to_sql() ); @@ -398,7 +417,7 @@ mod tests { Statement::CreateTable { if_not_exists: false, name: "Foo".into(), - columns: vec![ + columns: Some(vec![ ColumnDef { name: "id".to_owned(), data_type: DataType::Int, @@ -420,8 +439,9 @@ mod tests { default: None, unique: None, } - ], - source: None + ]), + source: None, + engine: None, } .to_sql() ); @@ -434,7 +454,7 @@ mod tests { Statement::CreateTable { if_not_exists: false, name: "Foo".into(), - columns: vec![], + columns: None, source: Some(Box::new(Query { body: SetExpr::Select(Box::new(Select { projection: vec![ @@ -462,7 +482,8 @@ mod tests { order_by: vec![], limit: None, offset: None - })) + })), + engine: None, } .to_sql() ); @@ -472,7 +493,7 @@ mod tests { Statement::CreateTable { if_not_exists: true, name: "Foo".into(), - columns: vec![], + columns: None, source: Some(Box::new(Query { body: SetExpr::Values(Values(vec![vec![Expr::Literal(AstLiteral::Boolean( true @@ -480,7 +501,41 @@ mod tests { order_by: vec![], limit: None, offset: None - })) + })), + engine: None, + } + .to_sql() + ); + } + + #[test] + fn to_sql_create_table_with_engine() { + assert_eq!( + "CREATE TABLE Foo ENGINE = MEMORY;", + Statement::CreateTable { + if_not_exists: false, + name: "Foo".into(), + columns: None, + source: None, + engine: Some("MEMORY".to_owned()), + } + .to_sql() + ); + + assert_eq!( + "CREATE TABLE Foo (id BOOLEAN NOT NULL) ENGINE = SLED;", + Statement::CreateTable { + if_not_exists: false, + name: "Foo".into(), + columns: Some(vec![ColumnDef { + name: "id".to_owned(), + data_type: DataType::Boolean, + nullable: false, + default: None, + unique: None, + },]), + source: None, + engine: Some("SLED".to_owned()), } .to_sql() ); diff --git a/core/src/ast_builder/create_table.rs b/core/src/ast_builder/create_table.rs index db41a2a02..b50924a3b 100644 --- a/core/src/ast_builder/create_table.rs +++ b/core/src/ast_builder/create_table.rs @@ -7,7 +7,7 @@ use { pub struct CreateTableNode { table_name: String, if_not_exists: bool, - columns: Vec, + columns: Option>, } impl CreateTableNode { @@ -15,12 +15,20 @@ impl CreateTableNode { Self { table_name, if_not_exists: not_exists, - columns: Vec::new(), + columns: None, } } pub fn add_column>(mut self, column: T) -> Self { - self.columns.push(column.into()); + match self.columns { + Some(ref mut columns) => { + columns.push(column.into()); + } + None => { + self.columns = Some(vec![column.into()]); + } + } + self } } @@ -28,17 +36,22 @@ impl CreateTableNode { impl Build for CreateTableNode { fn build(self) -> Result { let table_name = self.table_name; - let columns = self - .columns - .into_iter() - .map(TryInto::try_into) - .collect::>>()?; + let columns = match self.columns { + Some(columns) => Some( + columns + .into_iter() + .map(TryInto::try_into) + .collect::>>()?, + ), + None => None, + }; Ok(Statement::CreateTable { name: table_name, if_not_exists: self.if_not_exists, columns, source: None, + engine: None, }) } } diff --git a/core/src/ast_builder/expr/function.rs b/core/src/ast_builder/expr/function.rs index 916cb6aa7..de51b59ee 100644 --- a/core/src/ast_builder/expr/function.rs +++ b/core/src/ast_builder/expr/function.rs @@ -16,6 +16,7 @@ pub enum FunctionNode<'a> { then: ExprNode<'a>, }, Ceil(ExprNode<'a>), + Rand(Option>), Round(ExprNode<'a>), Floor(ExprNode<'a>), Asin(ExprNode<'a>), @@ -145,6 +146,9 @@ impl<'a> TryFrom> for Function { Ok(Function::IfNull { expr, then }) } FunctionNode::Ceil(expr_node) => expr_node.try_into().map(Function::Ceil), + FunctionNode::Rand(expr_node) => Ok(Function::Rand( + expr_node.map(TryInto::try_into).transpose()?, + )), FunctionNode::Round(expr_node) => expr_node.try_into().map(Function::Round), FunctionNode::Floor(expr_node) => expr_node.try_into().map(Function::Floor), FunctionNode::Asin(expr_node) => expr_node.try_into().map(Function::Asin), @@ -304,6 +308,9 @@ impl<'a> ExprNode<'a> { pub fn ceil(self) -> ExprNode<'a> { ceil(self) } + pub fn rand(self) -> ExprNode<'a> { + rand(Some(self)) + } pub fn round(self) -> ExprNode<'a> { round(self) } @@ -440,6 +447,9 @@ pub fn ifnull<'a, T: Into>, U: Into>>(expr: T, then: U pub fn ceil<'a, T: Into>>(expr: T) -> ExprNode<'a> { ExprNode::Function(Box::new(FunctionNode::Ceil(expr.into()))) } +pub fn rand(expr: Option) -> ExprNode { + ExprNode::Function(Box::new(FunctionNode::Rand(expr))) +} pub fn round<'a, T: Into>>(expr: T) -> ExprNode<'a> { ExprNode::Function(Box::new(FunctionNode::Round(expr.into()))) } @@ -710,7 +720,7 @@ mod tests { ast_builder::{ abs, acos, asin, atan, cast, ceil, col, concat, concat_ws, cos, date, degrees, divide, exp, expr, extract, floor, format, gcd, generate_uuid, ifnull, lcm, left, ln, log, - log10, log2, lower, lpad, ltrim, modulo, now, num, pi, position, power, radians, + log10, log2, lower, lpad, ltrim, modulo, now, num, pi, position, power, radians, rand, repeat, reverse, right, round, rpad, rtrim, sign, sin, sqrt, substr, tan, test_expr, text, time, timestamp, to_date, to_time, to_timestamp, upper, }, @@ -760,6 +770,21 @@ mod tests { test_expr(actual, expected); } + #[test] + fn function_rand() { + let actual = rand(None); + let expected = "RAND()"; + test_expr(actual, expected); + + let actual = rand(Some(col("num"))); + let expected = "RAND(num)"; + test_expr(actual, expected); + + let actual = expr("base - 10").rand(); + let expected = "RAND(base - 10)"; + test_expr(actual, expected); + } + #[test] fn function_round() { let actual = round(col("num")); @@ -1110,6 +1135,26 @@ mod tests { let actual = text("GlueSQL").substr(num(2), None); let expected = "SUBSTR('GlueSQL', 2)"; test_expr(actual, expected); + + let actual = text("GlueSQL").substr(num(2), None); + let expected = "SUBSTR('GlueSQL', 2)"; + test_expr(actual, expected); + + let actual = substr(text("GlueSQL ").rtrim(None), num(2), None); + let expected = "SUBSTR(RTRIM('GlueSQL '), 2)"; + test_expr(actual, expected); + + let actual = text("GlueSQL ").rtrim(None).substr(num(2), None); + let expected = "SUBSTR(RTRIM('GlueSQL '), 2)"; + test_expr(actual, expected); + + let actual = substr(text(" GlueSQL").ltrim(None), num(2), None); + let expected = "SUBSTR(LTRIM(' GlueSQL'), 2)"; + test_expr(actual, expected); + + let actual = text(" GlueSQL").ltrim(None).substr(num(2), None); + let expected = "SUBSTR(LTRIM(' GlueSQL'), 2)"; + test_expr(actual, expected); } #[test] @@ -1129,6 +1174,14 @@ mod tests { let actual = text("GlueSQLABC").rtrim(Some(text("ABC"))); let expected = "RTRIM('GlueSQLABC','ABC')"; test_expr(actual, expected); + + let actual = text("chicken").ltrim(None).rtrim(Some(text("en"))); + let expected = "RTRIM(LTRIM('chicken'),'en')"; + test_expr(actual, expected); + + let actual = rtrim(text("chicken").ltrim(Some(text("chick"))), None); + let expected = "RTRIM(LTRIM('chicken','chick'))"; + test_expr(actual, expected); } #[test] @@ -1148,6 +1201,14 @@ mod tests { let actual = text("ABCGlueSQL").ltrim(Some(text("ABC"))); let expected = "LTRIM('ABCGlueSQL','ABC')"; test_expr(actual, expected); + + let actual = text("chicken").rtrim(Some(text("en"))).ltrim(None); + let expected = "LTRIM(RTRIM('chicken','en'))"; + test_expr(actual, expected); + + let actual = text("chicken").rtrim(None).ltrim(Some(text("chick"))); + let expected = "LTRIM(RTRIM('chicken'),'chick')"; + test_expr(actual, expected); } #[test] diff --git a/core/src/ast_builder/mod.rs b/core/src/ast_builder/mod.rs index ac94a8d71..a6e1f79b3 100644 --- a/core/src/ast_builder/mod.rs +++ b/core/src/ast_builder/mod.rs @@ -78,8 +78,9 @@ pub use expr::{ function::{ abs, acos, asin, atan, cast, ceil, concat, concat_ws, cos, degrees, divide, exp, extract, floor, format, gcd, generate_uuid, ifnull, lcm, left, ln, log, log10, log2, lower, lpad, - ltrim, modulo, now, pi, position, power, radians, repeat, reverse, right, round, rpad, - rtrim, sign, sin, sqrt, substr, tan, to_date, to_time, to_timestamp, upper, FunctionNode, + ltrim, modulo, now, pi, position, power, radians, rand, repeat, reverse, right, round, + rpad, rtrim, sign, sin, sqrt, substr, tan, to_date, to_time, to_timestamp, upper, + FunctionNode, }, }; diff --git a/core/src/data/bigdecimal_ext.rs b/core/src/data/bigdecimal_ext.rs index 0e73813d4..a3999770a 100644 --- a/core/src/data/bigdecimal_ext.rs +++ b/core/src/data/bigdecimal_ext.rs @@ -9,6 +9,8 @@ pub trait BigDecimalExt { fn to_u8(&self) -> Option; fn to_u16(&self) -> Option; fn to_u32(&self) -> Option; + fn to_u128(&self) -> Option; + fn to_u32(&self) -> Option; fn to_u64(&self) -> Option; fn to_u128(&self) -> Option; fn to_f64(&self) -> Option; @@ -63,6 +65,14 @@ impl BigDecimalExt for BigDecimal { false => None, } } + fn to_u32(&self) -> Option { + self.is_integer() + .then(|| bigdecimal::ToPrimitive::to_u32(self))? + } + fn to_u128(&self) -> Option { + self.is_integer() + .then(|| bigdecimal::ToPrimitive::to_u128(self))? + } fn to_f64(&self) -> Option { bigdecimal::ToPrimitive::to_f64(self) } diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 2fe38c299..60cc62017 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -6,7 +6,7 @@ use { chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}, rust_decimal::Decimal, serde::{Deserialize, Serialize}, - std::{cmp::Ordering, fmt::Debug}, + std::{cmp::Ordering, fmt::Debug, net::IpAddr}, thiserror::Error as ThisError, }; @@ -38,6 +38,7 @@ pub enum Key { Bool(bool), Str(String), Bytea(Vec), + Inet(IpAddr), Date(NaiveDate), Timestamp(NaiveDateTime), Time(NaiveTime), @@ -62,6 +63,7 @@ impl PartialOrd for Key { (Key::Bool(l), Key::Bool(r)) => Some(l.cmp(r)), (Key::Str(l), Key::Str(r)) => Some(l.cmp(r)), (Key::Bytea(l), Key::Bytea(r)) => Some(l.cmp(r)), + (Key::Inet(l), Key::Inet(r)) => Some(l.cmp(r)), (Key::Date(l), Key::Date(r)) => Some(l.cmp(r)), (Key::Timestamp(l), Key::Timestamp(r)) => Some(l.cmp(r)), (Key::Time(l), Key::Time(r)) => Some(l.cmp(r)), @@ -93,6 +95,7 @@ impl TryFrom for Key { Decimal(v) => Ok(Key::Decimal(v)), Str(v) => Ok(Key::Str(v)), Bytea(v) => Ok(Key::Bytea(v)), + Inet(v) => Ok(Key::Inet(v)), Date(v) => Ok(Key::Date(v)), Timestamp(v) => Ok(Key::Timestamp(v)), Time(v) => Ok(Key::Time(v)), @@ -114,6 +117,31 @@ impl TryFrom<&Value> for Key { } } +impl From for Value { + fn from(key: Key) -> Self { + match key { + Key::Bool(v) => Value::Bool(v), + Key::I8(v) => Value::I8(v), + Key::I16(v) => Value::I16(v), + Key::I32(v) => Value::I32(v), + Key::I64(v) => Value::I64(v), + Key::I128(v) => Value::I128(v), + Key::U8(v) => Value::U8(v), + Key::U16(v) => Value::U16(v), + Key::Decimal(v) => Value::Decimal(v), + Key::Str(v) => Value::Str(v), + Key::Bytea(v) => Value::Bytea(v), + Key::Inet(v) => Value::Inet(v), + Key::Date(v) => Value::Date(v), + Key::Timestamp(v) => Value::Timestamp(v), + Key::Time(v) => Value::Time(v), + Key::Interval(v) => Value::Interval(v), + Key::Uuid(v) => Value::Uuid(v), + Key::None => Value::Null, + } + } +} + const VALUE: u8 = 0; const NONE: u8 = 1; @@ -223,6 +251,10 @@ impl Key { .copied() .collect::>(), Key::Bytea(v) => v.to_vec(), + Key::Inet(v) => match v { + IpAddr::V4(v) => v.octets().to_vec(), + IpAddr::V6(v) => v.octets().to_vec(), + }, Key::Date(date) => [VALUE] .iter() .chain(date.num_days_from_ce().to_be_bytes().iter()) @@ -285,8 +317,9 @@ mod tests { result::Result, translate::translate_expr, }, + chrono::{NaiveDate, NaiveDateTime, NaiveTime}, rust_decimal::Decimal, - std::{cmp::Ordering, collections::HashMap, str::FromStr}, + std::{cmp::Ordering, collections::HashMap, net::IpAddr, str::FromStr}, }; fn convert(sql: &str) -> Result { @@ -304,6 +337,7 @@ mod tests { assert_eq!(convert("CAST(11 AS INT16)"), Ok(Key::I16(11))); assert_eq!(convert("CAST(11 AS INT32)"), Ok(Key::I32(11))); assert_eq!(convert("2048"), Ok(Key::I64(2048))); + assert_eq!(convert("CAST(1024 AS INT128)"), Ok(Key::I128(1024))); assert_eq!(convert("CAST(11 AS UINT8)"), Ok(Key::U8(11))); assert_eq!(convert("CAST(11 AS UINT16)"), Ok(Key::U16(11))); assert_eq!(convert("CAST(11 AS UINT32)"), Ok(Key::U32(11))); @@ -313,6 +347,10 @@ mod tests { convert("CAST(123.45 AS DECIMAL)"), Ok(Key::Decimal(Decimal::from_str("123.45").unwrap())) ); + assert_eq!( + convert("CAST(0 AS INET)"), + Ok(Key::Inet(IpAddr::from_str("0.0.0.0").unwrap())) + ); assert_eq!( convert("'Hello World'"), @@ -347,6 +385,10 @@ mod tests { Key::try_from(Value::List(Vec::default())), Err(KeyError::ListTypeKeyNotSupported.into()) ); + assert_eq!( + convert("SUBSTR('BEEF', 2, 3)"), + Ok(Key::Str("EEF".to_owned())) + ); assert_eq!(convert("POSITION('PORK' IN 'MEAT')"), Ok(Key::I64(0))); assert_eq!( convert("EXTRACT(SECOND FROM INTERVAL '8' SECOND)"), @@ -370,10 +412,7 @@ mod tests { #[test] fn cmp_big_endian() { - use { - crate::data::{Interval as I, Key::*}, - chrono::{NaiveDate, NaiveTime}, - }; + use crate::data::{Interval as I, Key::*}; let null = None.to_cmp_be_bytes(); @@ -548,6 +587,20 @@ mod tests { assert_eq!(cmp(&n5, &n4), Ordering::Greater); assert_eq!(cmp(&n1, &null), Ordering::Less); + let n1 = Inet(IpAddr::from_str("192.168.0.1").unwrap()).to_cmp_be_bytes(); + let n2 = Inet(IpAddr::from_str("127.0.0.1").unwrap()).to_cmp_be_bytes(); + let n3 = Inet(IpAddr::from_str("10.0.0.1").unwrap()).to_cmp_be_bytes(); + let n4 = Inet(IpAddr::from_str("0.0.0.0").unwrap()).to_cmp_be_bytes(); + let n5 = Inet(IpAddr::from_str("0:0:0:0:0:0:0:1").unwrap()).to_cmp_be_bytes(); + let n6 = Inet(IpAddr::from_str("::1").unwrap()).to_cmp_be_bytes(); + + assert_eq!(cmp(&n1, &n1), Ordering::Equal); + assert_eq!(cmp(&n2, &n1), Ordering::Less); + assert_eq!(cmp(&n2, &n3), Ordering::Greater); + assert_eq!(cmp(&n3, &n4), Ordering::Greater); + assert_eq!(cmp(&n1, &null), Ordering::Greater); + assert_eq!(cmp(&n5, &n6), Ordering::Equal); + let n1 = Date(NaiveDate::from_ymd_opt(2021, 1, 1).unwrap()).to_cmp_be_bytes(); let n2 = Date(NaiveDate::from_ymd_opt(1989, 3, 20).unwrap()).to_cmp_be_bytes(); @@ -600,4 +653,64 @@ mod tests { assert_eq!(cmp(&n2, &n1), Ordering::Greater); assert_eq!(cmp(&n1, &null), Ordering::Less); } + + #[test] + fn from_key_to_value() { + use {crate::data::Interval as I, uuid::Uuid}; + + assert_eq!(Value::from(Key::I8(2)), Value::I8(2)); + assert_eq!(Value::from(Key::I16(4)), Value::I16(4)); + assert_eq!(Value::from(Key::I32(8)), Value::I32(8)); + assert_eq!(Value::from(Key::I64(16)), Value::I64(16)); + assert_eq!(Value::from(Key::I128(32)), Value::I128(32)); + assert_eq!(Value::from(Key::U8(64)), Value::U8(64)); + assert_eq!(Value::from(Key::U16(128)), Value::U16(128)); + assert_eq!( + Value::from(Key::Decimal(Decimal::from_str("123.45").unwrap())), + Value::Decimal(Decimal::from_str("123.45").unwrap()) + ); + assert_eq!(Value::from(Key::Bool(true)), Value::Bool(true)); + assert_eq!( + Value::from(Key::Str("abc".to_owned())), + Value::Str("abc".to_owned()) + ); + assert_eq!(Value::from(Key::Bytea(vec![])), Value::Bytea(vec![])); + assert_eq!( + Value::from(Key::Inet(IpAddr::from_str("::1").unwrap())), + Value::Inet(IpAddr::from_str("::1").unwrap()) + ); + assert_eq!( + Value::from(Key::Date(NaiveDate::from_ymd_opt(2023, 1, 23).unwrap())), + Value::Date(NaiveDate::from_ymd_opt(2023, 1, 23).unwrap()) + ); + assert_eq!( + Value::from(Key::Timestamp( + NaiveDateTime::from_timestamp_millis(1662921288).unwrap() + )), + Value::Timestamp(NaiveDateTime::from_timestamp_millis(1662921288).unwrap()) + ); + assert_eq!( + Value::from(Key::Time( + NaiveTime::from_hms_milli_opt(20, 20, 1, 452).unwrap() + )), + Value::Time(NaiveTime::from_hms_milli_opt(20, 20, 1, 452).unwrap()) + ); + assert_eq!( + Value::from(Key::Interval(I::Month(11))), + Value::Interval(I::Month(11)) + ); + assert_eq!( + Value::from(Key::Uuid( + Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000") + .unwrap() + .as_u128() + )), + Value::Uuid( + Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000") + .unwrap() + .as_u128() + ) + ); + matches!(Value::from(Key::None), Value::Null); + } } diff --git a/core/src/data/literal.rs b/core/src/data/literal.rs index 50c3e44ef..8b10aff12 100644 --- a/core/src/data/literal.rs +++ b/core/src/data/literal.rs @@ -39,7 +39,7 @@ pub enum LiteralError { pub enum Literal<'a> { Boolean(bool), Number(Cow<'a, BigDecimal>), - Text(Cow<'a, String>), + Text(Cow<'a, str>), Bytea(Vec), Null, } @@ -62,6 +62,36 @@ impl<'a> TryFrom<&'a AstLiteral> for Literal<'a> { } } +impl PartialEq for Literal<'_> { + fn eq(&self, other: &str) -> bool { + match (self, other) { + (Literal::Text(l), r) => l.as_ref() == r, + _ => false, + } + } +} + +impl PartialOrd for Literal<'_> { + fn partial_cmp(&self, other: &str) -> Option { + match (self, other) { + (Literal::Text(l), r) => Some(l.as_ref().cmp(r)), + _ => None, + } + } +} + +impl> PartialEq for Literal<'_> { + fn eq(&self, other: &T) -> bool { + PartialEq::::eq(self, other.as_ref()) + } +} + +impl> PartialOrd for Literal<'_> { + fn partial_cmp(&self, other: &T) -> Option { + PartialOrd::::partial_cmp(self, other.as_ref()) + } +} + impl PartialEq> for Literal<'_> { fn eq(&self, other: &Literal) -> bool { match (self, other) { @@ -229,7 +259,7 @@ mod tests { ); test( AstLiteral::QuotedString("abc".to_owned()), - Ok(Text(Cow::Borrowed(&("abc".to_owned())))), + Ok(Text(Cow::Borrowed("abc"))), ); test( AstLiteral::HexString("1A2B".to_owned()), @@ -366,7 +396,7 @@ mod tests { //Boolean assert_eq!(Boolean(true), Boolean(true)); - assert!(Boolean(true) != Boolean(false)); + assert_ne!(Boolean(true), Boolean(false)); //Number assert_eq!(num!("123"), num!("123")); assert_eq!(num!("12.0"), num!("12.0")); @@ -419,6 +449,8 @@ mod tests { ); assert_eq!(Boolean(true).partial_cmp(&num!("1")), None); assert_eq!(Boolean(true).partial_cmp(&text!("Foo")), None); + assert_eq!(Boolean(true).partial_cmp("true"), None); + assert_eq!(Boolean(true).partial_cmp(&"true".to_owned()), None); assert_eq!(Boolean(true).partial_cmp(&Null), None); //Number - valid format -> (int, int), (float, int), (int, float), (float, float) assert_eq!(num!("123").partial_cmp(&num!("1234")), Some(Ordering::Less)); @@ -443,6 +475,9 @@ mod tests { assert_eq!(text!("a").partial_cmp(&text!("a")), Some(Ordering::Equal)); assert_eq!(text!("b").partial_cmp(&text!("a")), Some(Ordering::Greater)); assert_eq!(text!("a").partial_cmp(&Null), None); + assert_eq!(text!("b").partial_cmp("b"), Some(Ordering::Equal)); + assert_eq!(text!("a").partial_cmp("b"), Some(Ordering::Less)); + assert_eq!(text!("c").partial_cmp("b"), Some(Ordering::Greater)); //Bytea assert_eq!( bytea!("12").partial_cmp(&bytea!("20")), diff --git a/core/src/data/schema.rs b/core/src/data/schema.rs index 83d9bfde6..e692cbfff 100644 --- a/core/src/data/schema.rs +++ b/core/src/data/schema.rs @@ -27,6 +27,7 @@ pub struct Schema { pub table_name: String, pub column_defs: Option>, pub indexes: Vec, + pub engine: Option, pub created: NaiveDateTime, } @@ -34,15 +35,17 @@ impl Schema { pub fn to_ddl(self) -> String { let Schema { table_name, - column_defs: columns, + column_defs, indexes, + engine, .. } = self; let create_table = Statement::CreateTable { if_not_exists: false, name: table_name.clone(), - columns: columns.unwrap_or_default(), + columns: column_defs, + engine, source: None, } .to_sql(); @@ -91,6 +94,7 @@ mod tests { }, ]), indexes: Vec::new(), + engine: None, created: Utc::now().naive_utc(), }; @@ -103,6 +107,7 @@ mod tests { table_name: "Test".to_owned(), column_defs: None, indexes: Vec::new(), + engine: None, created: Utc::now().naive_utc(), }; assert_eq!(schema.to_ddl(), "CREATE TABLE Test;"); @@ -120,6 +125,7 @@ mod tests { unique: Some(ColumnUniqueOption { is_primary: true }), }]), indexes: Vec::new(), + engine: None, created: Utc::now().naive_utc(), }; @@ -163,6 +169,7 @@ mod tests { created: Utc::now().naive_utc(), }, ], + engine: None, created: Utc::now().naive_utc(), }; diff --git a/core/src/data/string_ext.rs b/core/src/data/string_ext.rs index cb9206d7b..3f3c2969f 100644 --- a/core/src/data/string_ext.rs +++ b/core/src/data/string_ext.rs @@ -10,7 +10,7 @@ pub trait StringExt { fn like(&self, pattern: &str, case_sensitive: bool) -> Result; } -impl StringExt for String { +impl StringExt for str { fn like(&self, pattern: &str, case_sensitive: bool) -> Result { let (match_string, match_pattern) = match case_sensitive { true => (self.to_owned(), pattern.to_owned()), diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 37efafb47..ec894cbb6 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -1,6 +1,7 @@ use { super::{ date::{parse_date, parse_time, parse_timestamp}, + uuid::parse_uuid, Value, ValueError, }, crate::{ @@ -9,6 +10,7 @@ use { }, chrono::{NaiveDate, NaiveDateTime, NaiveTime}, rust_decimal::prelude::{Decimal, FromPrimitive, FromStr, ToPrimitive}, + std::net::IpAddr, uuid::Uuid, }; @@ -17,6 +19,7 @@ impl From<&Value> for String { match v { Value::Str(value) => value.to_owned(), Value::Bytea(value) => hex::encode(value), + Value::Inet(value) => value.to_string(), Value::Bool(value) => (if *value { "TRUE" } else { "FALSE" }).to_owned(), Value::I8(value) => value.to_string(), Value::I16(value) => value.to_string(), @@ -138,6 +141,7 @@ impl TryFrom<&Value> for bool { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -172,6 +176,7 @@ impl TryFrom<&Value> for i8 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -206,6 +211,7 @@ impl TryFrom<&Value> for i16 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -240,6 +246,7 @@ impl TryFrom<&Value> for i32 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -274,6 +281,7 @@ impl TryFrom<&Value> for i64 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -308,6 +316,7 @@ impl TryFrom<&Value> for i128 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -342,6 +351,7 @@ impl TryFrom<&Value> for u8 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -375,6 +385,7 @@ impl TryFrom<&Value> for u16 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -517,6 +528,7 @@ impl TryFrom<&Value> for f64 { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -551,6 +563,7 @@ impl TryFrom<&Value> for usize { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -591,6 +604,7 @@ impl TryFrom<&Value> for Decimal { | Value::Map(_) | Value::List(_) | Value::Bytea(_) + | Value::Inet(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) } @@ -662,6 +676,19 @@ impl TryFrom<&Value> for Interval { } } +impl TryFrom<&Value> for u32 { + type Error = Error; + + fn try_from(v: &Value) -> Result { + match v { + Value::Inet(IpAddr::V4(v)) => Ok(u32::from(*v)), + _ => Err(ValueError::ImpossibleCast.into()), + } + } +} + +impl TryFrom<&Value> for u128 { + type Error = Error; // impl TryFrom<&Value> for u128 { // type Error = Error; @@ -672,15 +699,40 @@ impl TryFrom<&Value> for Interval { // } // } // } + fn try_from(v: &Value) -> Result { + match v { + Value::Uuid(value) => Ok(*value), + Value::Str(value) => parse_uuid(value), + Value::Inet(IpAddr::V6(v)) => Ok(u128::from(*v)), + _ => Err(ValueError::ImpossibleCast.into()), + } + } +} + +impl TryFrom<&Value> for IpAddr { + type Error = Error; + + fn try_from(v: &Value) -> Result { + Ok(match v { + Value::Inet(value) => *value, + Value::Str(value) => IpAddr::from_str(value).map_err(|_| ValueError::ImpossibleCast)?, + _ => return Err(ValueError::ImpossibleCast.into()), + }) + } +} #[cfg(test)] mod tests { use { - super::{Value, ValueError}, + super::{parse_uuid, Value, ValueError}, crate::{data::Interval as I, result::Result}, chrono::{self, NaiveDate, NaiveDateTime, NaiveTime}, rust_decimal::Decimal, - std::collections::HashMap, + std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + str::FromStr, + }, }; fn timestamp(y: i32, m: u32, d: u32, hh: u32, mm: u32, ss: u32, ms: u32) -> NaiveDateTime { @@ -708,6 +760,7 @@ mod tests { test!(Value::Str("text".to_owned()), "text"); test!(Value::Bytea(hex::decode("1234").unwrap()), "1234"); + test!(Value::Inet(IpAddr::from_str("::1").unwrap()), "::1"); test!(Value::Bool(true), "TRUE"); test!(Value::I8(122), "122"); test!(Value::I16(122), "122"); @@ -815,6 +868,10 @@ mod tests { test!(Value::I32(3), Err(ValueError::ImpossibleCast.into())); test!(Value::I64(3), Err(ValueError::ImpossibleCast.into())); test!(Value::I128(3), Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -883,6 +940,10 @@ mod tests { test!(Value::U64(128), Err(ValueError::ImpossibleCast.into())); test!(Value::U128(128), Err(ValueError::ImpossibleCast.into())); test!(Value::F64(128.0), Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -939,6 +1000,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -996,6 +1061,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1053,6 +1122,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1110,6 +1183,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1178,6 +1255,10 @@ mod tests { test!(Value::I64(256), Err(ValueError::ImpossibleCast.into())); test!(Value::I128(256), Err(ValueError::ImpossibleCast.into())); test!(Value::F64(256.0), Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1346,6 +1427,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1460,6 +1545,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1514,6 +1603,10 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + test!( + Value::Inet(IpAddr::from_str("::1").unwrap()), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1581,4 +1674,58 @@ mod tests { Ok(I::Month(274)) ); } + + #[test] + fn try_into_u32() { + assert_eq!( + u32::try_from(&Value::Inet(IpAddr::from_str("0.0.0.0").unwrap())), + Ok(u32::from(Ipv4Addr::from(0))) + ); + assert_eq!( + u32::try_from(&Value::Inet(IpAddr::from_str("::0").unwrap())), + Err(ValueError::ImpossibleCast.into()) + ); + } + + #[test] + fn try_into_u128() { + let uuid = 195965723427462096757863453463987888808; + assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); + assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); + + let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; + assert_eq!( + u128::try_from(&Value::Str(uuid.to_owned())), + parse_uuid(uuid) + ); + + let ip = Ipv6Addr::from(9876543210); + assert_eq!( + u128::try_from(&Value::Inet(IpAddr::V6(ip))), + Ok(u128::from(ip)) + ); + + assert_eq!( + u128::try_from(&Value::Date(date(2021, 11, 20))), + Err(ValueError::ImpossibleCast.into()) + ); + } + + #[test] + fn try_into_ipaddr() { + macro_rules! test { + ($from: expr, $to: expr) => { + assert_eq!(IpAddr::try_from($from), Ok(IpAddr::from_str($to).unwrap())); + assert_eq!(IpAddr::try_from($from), Ok(IpAddr::from_str($to).unwrap())) + }; + } + test!(&Value::Str("127.0.0.1".to_owned()), "127.0.0.1"); + test!(&Value::Str("0.0.0.0".to_owned()), "0.0.0.0"); + test!(IpAddr::from_str("::1").unwrap(), "::1"); + test!(IpAddr::from_str("::2:4cb0:16ea").unwrap(), "::2:4cb0:16ea"); + assert_eq!( + IpAddr::try_from(&Value::Date(date(2021, 11, 20))), + Err(ValueError::ImpossibleCast.into()) + ); + } } diff --git a/core/src/data/value/error.rs b/core/src/data/value/error.rs index 17b712e67..2678b0c34 100644 --- a/core/src/data/value/error.rs +++ b/core/src/data/value/error.rs @@ -47,6 +47,9 @@ pub enum ValueError { #[error("failed to parse hex string: {0}")] FailedToParseHexString(String), + #[error("failed to parse inet string: {0}")] + FailedToParseInetString(String), + #[error("non-numeric values {lhs:?} {operator} {rhs:?}")] NonNumericMathOperation { lhs: Value, @@ -85,6 +88,9 @@ pub enum ValueError { #[error("unimplemented cast")] UnimplementedCast, + #[error("failed to cast from hex string to bytea: {0}")] + CastFromHexToByteaFailed(String), + #[error("function CONCAT requires at least 1 argument")] EmptyArgNotAllowedInConcat, diff --git a/core/src/data/value/expr.rs b/core/src/data/value/expr.rs index 6450cf152..0e7f53c6e 100644 --- a/core/src/data/value/expr.rs +++ b/core/src/data/value/expr.rs @@ -59,6 +59,7 @@ impl TryFrom for Expr { )), Value::Str(v) => Expr::Literal(AstLiteral::QuotedString(v)), Value::Bytea(v) => Expr::Literal(AstLiteral::HexString(hex::encode(v))), + Value::Inet(v) => Expr::Literal(AstLiteral::QuotedString(v.to_string())), Value::Date(v) => Expr::TypedString { data_type: DataType::Date, value: v.to_string(), diff --git a/core/src/data/value/json.rs b/core/src/data/value/json.rs index 62700164b..94820d359 100644 --- a/core/src/data/value/json.rs +++ b/core/src/data/value/json.rs @@ -70,6 +70,7 @@ impl TryFrom for JsonValue { .map_err(|_| ValueError::UnreachableJsonNumberParseFailure(v.to_string()).into()), Value::Str(v) => Ok(v.into()), Value::Bytea(v) => Ok(hex::encode(v).into()), + Value::Inet(v) => Ok(v.to_string().into()), Value::Date(v) => Ok(v.to_string().into()), Value::Timestamp(v) => Ok(DateTime::::from_utc(v, Utc).to_string().into()), Value::Time(v) => Ok(v.to_string().into()), @@ -128,6 +129,7 @@ mod tests { chrono::{NaiveDate, NaiveTime}, rust_decimal::Decimal, serde_json::{json, Number as JsonNumber, Value as JsonValue}, + std::{net::IpAddr, str::FromStr}, }; #[test] @@ -197,6 +199,10 @@ mod tests { Value::Bytea(hex::decode("a1b2").unwrap()).try_into(), Ok(JsonValue::String("a1b2".to_owned())) ); + assert_eq!( + Value::Inet(IpAddr::from_str("::1").unwrap()).try_into(), + Ok(JsonValue::String("::1".to_owned())) + ); assert_eq!( Value::Date(NaiveDate::from_ymd_opt(2020, 1, 3).unwrap()).try_into(), Ok(JsonValue::String("2020-01-03".to_owned())) diff --git a/core/src/data/value/literal.rs b/core/src/data/value/literal.rs index e1984d730..4444fd2d9 100644 --- a/core/src/data/value/literal.rs +++ b/core/src/data/value/literal.rs @@ -11,7 +11,11 @@ use { }, chrono::NaiveDate, rust_decimal::Decimal, - std::cmp::Ordering, + std::{ + cmp::Ordering, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + str::FromStr, + }, }; impl PartialEq> for Value { @@ -44,6 +48,19 @@ impl PartialEq> for Value { None => false, }, (Value::Uuid(l), Literal::Text(r)) => parse_uuid(r).map(|r| l == &r).unwrap_or(false), + (Value::Inet(l), Literal::Text(r)) => match IpAddr::from_str(r) { + Ok(x) => l == &x, + Err(_) => false, + }, + (Value::Inet(l), Literal::Number(r)) => { + if let Some(x) = r.to_u32() { + l == &Ipv4Addr::from(x) + } else if let Some(x) = r.to_u128() { + l == &Ipv6Addr::from(x) + } else { + false + } + } _ => false, } } @@ -86,7 +103,10 @@ impl PartialOrd> for Value { (Value::F64(l), Literal::Number(r)) => { r.to_f64().map(|r| l.partial_cmp(&r)).unwrap_or(None) } - (Value::Str(l), Literal::Text(r)) => Some(l.cmp(r.as_ref())), + (Value::Str(l), Literal::Text(r)) => { + let l: &str = l.as_ref(); + Some(l.cmp(r)) + } (Value::Date(l), Literal::Text(r)) => match r.parse::() { Ok(r) => l.partial_cmp(&r), Err(_) => None, @@ -102,6 +122,19 @@ impl PartialOrd> for Value { (Value::Uuid(l), Literal::Text(r)) => { parse_uuid(r).map(|r| l.partial_cmp(&r)).unwrap_or(None) } + (Value::Inet(l), Literal::Text(r)) => match IpAddr::from_str(r) { + Ok(x) => l.partial_cmp(&x), + Err(_) => None, + }, + (Value::Inet(l), Literal::Number(r)) => { + if let Some(x) = r.to_u32() { + l.partial_cmp(&Ipv4Addr::from(x)) + } else if let Some(x) = r.to_u128() { + l.partial_cmp(&Ipv6Addr::from(x)) + } else { + None + } + } _ => None, } } @@ -189,6 +222,18 @@ impl Value { (DataType::Bytea, Literal::Text(v)) => hex::decode(v.as_ref()) .map(Value::Bytea) .map_err(|_| ValueError::FailedToParseHexString(v.to_string()).into()), + (DataType::Inet, Literal::Text(v)) => IpAddr::from_str(v.as_ref()) + .map(Value::Inet) + .map_err(|_| ValueError::FailedToParseInetString(v.to_string()).into()), + (DataType::Inet, Literal::Number(v)) => { + if let Some(x) = v.to_u32() { + Ok(Value::Inet(IpAddr::V4(Ipv4Addr::from(x)))) + } else { + Ok(Value::Inet(IpAddr::V6(Ipv6Addr::from( + v.to_u128().unwrap(), + )))) + } + } (DataType::Date, Literal::Text(v)) => v .parse::() .map(Value::Date) @@ -407,7 +452,7 @@ impl Value { Ok(Value::Str(v.to_owned())) } (DataType::Interval, Literal::Text(v)) => { - Interval::try_from(v.as_str()).map(Value::Interval) + Interval::try_from(v.as_ref()).map(Value::Interval) } (DataType::Uuid, Literal::Text(v)) => parse_uuid(v).map(Value::Uuid), (DataType::Boolean, Literal::Null) @@ -433,6 +478,18 @@ impl Value { (DataType::Timestamp, Literal::Text(v)) => parse_timestamp(v) .map(Value::Timestamp) .ok_or_else(|| ValueError::LiteralCastToTimestampFailed(v.to_string()).into()), + (DataType::Inet, Literal::Number(v)) => { + if let Some(x) = v.to_u32() { + Ok(Value::Inet(IpAddr::V4(Ipv4Addr::from(x)))) + } else if let Some(x) = v.to_u128() { + Ok(Value::Inet(IpAddr::V6(Ipv6Addr::from(x)))) + } else { + Err(ValueError::FailedToParseInetString(v.to_string()).into()) + } + } + (DataType::Inet, Literal::Text(v)) => IpAddr::from_str(v) + .map(Value::Inet) + .map_err(|_| ValueError::FailedToParseInetString(v.to_string()).into()), _ => Err(ValueError::UnimplementedLiteralCast { data_type: data_type.clone(), literal: format!("{:?}", literal), @@ -448,6 +505,7 @@ mod tests { crate::{data::Literal, prelude::Value}, bigdecimal::BigDecimal, chrono::{NaiveDate, NaiveDateTime, NaiveTime}, + std::net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; fn date(year: i32, month: u32, day: u32) -> NaiveDate { @@ -488,20 +546,28 @@ mod tests { let uuid = parse_uuid(uuid_text).unwrap(); let bytea = || hex::decode("123456").unwrap(); + let inet = |v: &str| Value::Inet(IpAddr::from_str(v).unwrap()); assert_eq!(Value::Bool(true), Literal::Boolean(true)); assert_eq!(Value::I8(8), num!("8")); assert_eq!(Value::I32(32), num!("32")); // should this work? + assert_eq!(Value::I16(16), num!("16")); + assert_eq!(Value::I32(32), num!("32")); assert_eq!(Value::I64(64), num!("64")); assert_eq!(Value::I128(128), num!("128")); - assert_eq!(Value::F64(7.123), num!("7.123")); assert_eq!(Value::U8(7), num!("7")); assert_eq!(Value::U16(64), num!("64")); assert_eq!(Value::U32(64), num!("64")); assert_eq!(Value::U64(64), num!("64")); assert_eq!(Value::U128(64), num!("64")); + assert_eq!(Value::F64(7.123), num!("7.123")); assert_eq!(Value::Str("Hello".to_owned()), text!("Hello")); assert_eq!(Value::Bytea(bytea()), Literal::Bytea(bytea())); + assert_eq!(inet("127.0.0.1"), text!("127.0.0.1")); + assert_eq!(inet("::1"), text!("::1")); + assert_eq!(inet("0.0.0.0"), num!("0")); + assert_ne!(inet("::1"), num!("0")); + assert_eq!(inet("::2:4cb0:16ea"), num!("9876543210")); assert_eq!(Value::Date(date(2021, 11, 20)), text!("2021-11-20")); assert_ne!(Value::Date(date(2021, 11, 20)), text!("202=abcdef")); assert_eq!( @@ -596,6 +662,7 @@ mod tests { } let bytea = |v| hex::decode(v).unwrap(); + let inet = |v| IpAddr::from_str(v).unwrap(); test!(DataType::Boolean, Literal::Boolean(true), Value::Bool(true)); test!(DataType::Int, num!("123456789"), Value::I64(123456789)); @@ -625,6 +692,26 @@ mod tests { Value::try_from_literal(&DataType::Bytea, &text!("123")), Err(ValueError::FailedToParseHexString("123".to_owned()).into()) ); + test!(DataType::Inet, text!("::1"), Value::Inet(inet("::1"))); + test!( + DataType::Inet, + num!("4294967295"), + Value::Inet(inet("255.255.255.255")) + ); + test!( + DataType::Inet, + num!("9876543210"), + Value::Inet(inet("::2:4cb0:16ea")) + ); + test!( + DataType::Inet, + num!("9876543210"), + Value::Inet(inet("::2:4cb0:16ea")) + ); + assert_eq!( + Value::try_from_literal(&DataType::Inet, &text!("123")), + Err(ValueError::FailedToParseInetString("123".to_owned()).into()) + ); test!( DataType::Date, text!("2015-09-05"), @@ -882,5 +969,20 @@ mod tests { text!("2022-12-20 10:00:00.987"), Value::Timestamp(timestamp(2022, 12, 20, 10, 0, 0, 987)) ); + test!( + DataType::Inet, + num!("1234567890"), + Value::Inet(IpAddr::from(Ipv4Addr::from(1234567890))) + ); + test!( + DataType::Inet, + num!("91234567890"), + Value::Inet(IpAddr::from(Ipv6Addr::from(91234567890))) + ); + test!( + DataType::Inet, + text!("::1"), + Value::Inet(IpAddr::from_str("::1").unwrap()) + ); } } diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 914631f39..f1113cdae 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -9,7 +9,7 @@ use { core::ops::Sub, rust_decimal::Decimal, serde::{Deserialize, Serialize}, - std::{cmp::Ordering, collections::HashMap, fmt::Debug}, + std::{cmp::Ordering, collections::HashMap, fmt::Debug, net::IpAddr}, }; mod binary_op; @@ -20,6 +20,7 @@ mod expr; mod json; mod literal; mod selector; +mod string; mod uuid; pub use { @@ -44,6 +45,7 @@ pub enum Value { Decimal(Decimal), Str(String), Bytea(Vec), + Inet(IpAddr), Date(NaiveDate), Timestamp(NaiveDateTime), Time(NaiveTime), @@ -72,6 +74,7 @@ impl PartialEq for Value { (Value::Bool(l), Value::Bool(r)) => l == r, (Value::Str(l), Value::Str(r)) => l == r, (Value::Bytea(l), Value::Bytea(r)) => l == r, + (Value::Inet(l), Value::Inet(r)) => l == r, (Value::Date(l), Value::Date(r)) => l == r, (Value::Date(l), Value::Timestamp(r)) => l .and_hms_opt(0, 0, 0) @@ -110,6 +113,7 @@ impl PartialOrd for Value { (Value::Bool(l), Value::Bool(r)) => Some(l.cmp(r)), (Value::Str(l), Value::Str(r)) => Some(l.cmp(r)), (Value::Bytea(l), Value::Bytea(r)) => Some(l.cmp(r)), + (Value::Inet(l), Value::Inet(r)) => Some(l.cmp(r)), (Value::Date(l), Value::Date(r)) => Some(l.cmp(r)), (Value::Date(l), Value::Timestamp(r)) => { l.and_hms_opt(0, 0, 0).map(|date_time| date_time.cmp(r)) @@ -162,6 +166,7 @@ impl Value { Value::Bool(_) => Some(DataType::Boolean), Value::Str(_) => Some(DataType::Text), Value::Bytea(_) => Some(DataType::Bytea), + Value::Inet(_) => Some(DataType::Inet), Value::Date(_) => Some(DataType::Date), Value::Timestamp(_) => Some(DataType::Timestamp), Value::Time(_) => Some(DataType::Time), @@ -190,6 +195,7 @@ impl Value { Value::Bool(_) => matches!(data_type, DataType::Boolean), Value::Str(_) => matches!(data_type, DataType::Text), Value::Bytea(_) => matches!(data_type, DataType::Bytea), + Value::Inet(_) => matches!(data_type, DataType::Inet), Value::Date(_) => matches!(data_type, DataType::Date), Value::Timestamp(_) => matches!(data_type, DataType::Timestamp), Value::Time(_) => matches!(data_type, DataType::Time), @@ -236,6 +242,7 @@ impl Value { | (DataType::Boolean, Value::Bool(_)) | (DataType::Text, Value::Str(_)) | (DataType::Bytea, Value::Bytea(_)) + | (DataType::Inet, Value::Inet(_)) | (DataType::Date, Value::Date(_)) | (DataType::Timestamp, Value::Timestamp(_)) | (DataType::Time, Value::Time(_)) @@ -262,6 +269,10 @@ impl Value { (DataType::Timestamp, value) => value.try_into().map(Value::Timestamp), (DataType::Interval, value) => value.try_into().map(Value::Interval), (DataType::Uuid, value) => value.try_into().map(Value::Uuid), + (DataType::Inet, value) => value.try_into().map(Value::Inet), + (DataType::Bytea, Value::Str(value)) => hex::decode(value) + .map_err(|_| ValueError::CastFromHexToByteaFailed(value.clone()).into()) + .map(Value::Bytea), _ => Err(ValueError::UnimplementedCast.into()), } @@ -710,6 +721,7 @@ mod tests { crate::data::{value::uuid::parse_uuid, ValueError}, chrono::{NaiveDate, NaiveTime}, rust_decimal::Decimal, + std::{net::IpAddr, str::FromStr}, }; fn time(hour: u32, min: u32, sec: u32) -> NaiveTime { @@ -729,6 +741,7 @@ mod tests { }; let decimal = |n: i32| Decimal(n.into()); let bytea = |v: &str| Bytea(hex::decode(v).unwrap()); + let inet = |v: &str| Inet(IpAddr::from_str(v).unwrap()); assert_ne!(Null, Null); assert_eq!(Bool(true), Bool(true)); @@ -747,6 +760,7 @@ mod tests { assert_eq!(F64(6.11), F64(6.11)); assert_eq!(Str("Glue".to_owned()), Str("Glue".to_owned())); assert_eq!(bytea("1004"), bytea("1004")); + assert_eq!(inet("::1"), inet("::1")); assert_eq!(Interval::Month(1), Interval::Month(1)); assert_eq!( Time(NaiveTime::from_hms_opt(12, 30, 11).unwrap()), @@ -823,6 +837,17 @@ mod tests { Some(Ordering::Greater) ); assert_eq!(bytea("10").partial_cmp(&bytea("10")), Some(Ordering::Equal)); + + let inet = |v: &str| Inet(IpAddr::from_str(v).unwrap()); + assert_eq!( + inet("0.0.0.0").partial_cmp(&inet("127.0.0.1")), + Some(Ordering::Less) + ); + assert_eq!( + inet("192.168.0.1").partial_cmp(&inet("127.0.0.1")), + Some(Ordering::Greater) + ); + assert_eq!(inet("::1").partial_cmp(&inet("::1")), Some(Ordering::Equal)); } #[test] @@ -1590,11 +1615,13 @@ mod tests { } let bytea = Value::Bytea(hex::decode("0abc").unwrap()); + let inet = |v| Value::Inet(IpAddr::from_str(v).unwrap()); // Same as cast!(Bool(true) => Boolean , Bool(true)); cast!(Str("a".to_owned()) => Text , Str("a".to_owned())); cast!(bytea => Bytea , bytea); + cast!(inet("::1") => Inet , inet("::1")); cast!(I8(1) => Int8 , I8(1)); cast!(I16(1) => Int16 , I16(1)); cast!(I32(1) => Int32 , I32(1)); @@ -1689,6 +1716,7 @@ mod tests { cast!(U8(11) => Text, Str("11".to_owned())); cast!(U16(11) => Text, Str("11".to_owned())); cast!(F64(1.0) => Text, Str("1".to_owned())); + cast!(inet("::1") => Text, Str("::1".to_owned())); let date = Value::Date(NaiveDate::from_ymd_opt(2021, 5, 1).unwrap()); cast!(date => Text, Str("2021-05-01".to_owned())); @@ -1723,6 +1751,24 @@ mod tests { cast!(Value::Date(NaiveDate::from_ymd_opt(2021, 5, 1).unwrap()) => Timestamp, Value::Timestamp(NaiveDate::from_ymd_opt(2021, 5, 1).unwrap().and_hms_opt(0, 0, 0).unwrap())); cast!(Str("2021-05-01 08:05:30".to_owned()) => Timestamp, Value::Timestamp(NaiveDate::from_ymd_opt(2021, 5, 1).unwrap().and_hms_opt(8, 5, 30).unwrap())); cast!(Null => Timestamp, Null); + + // Bytea + cast!(Value::Str("0abc".to_owned()) => Bytea, Value::Bytea(hex::decode("0abc").unwrap())); + assert_eq!( + Value::Str("!@#$5".to_owned()).cast(&Bytea), + Err(ValueError::CastFromHexToByteaFailed("!@#$5".to_owned()).into()), + ); + + // Inet + cast!(inet("::1") => Inet, inet("::1")); + cast!(Str("::1".to_owned()) => Inet, inet("::1")); + cast!(Str("0.0.0.0".to_owned()) => Inet, inet("0.0.0.0")); + + // Casting error + assert_eq!( + Value::Uuid(123).cast(&List), + Err(ValueError::UnimplementedCast.into()) + ); } #[test] @@ -1772,6 +1818,7 @@ mod tests { let map = Value::parse_json_map(r#"{ "a": 10 }"#).unwrap(); let list = Value::parse_json_list(r#"[ true ]"#).unwrap(); let bytea = Bytea(hex::decode("9001").unwrap()); + let inet = Inet(IpAddr::from_str("::1").unwrap()); assert!(Bool(true).validate_type(&D::Boolean).is_ok()); assert!(Bool(true).validate_type(&D::Int).is_err()); @@ -1806,6 +1853,10 @@ mod tests { assert!(Str("a".to_owned()).validate_type(&D::Int).is_err()); assert!(bytea.validate_type(&D::Bytea).is_ok()); assert!(bytea.validate_type(&D::Uuid).is_err()); + assert!(inet.validate_type(&D::Inet).is_ok()); + assert!(inet.validate_type(&D::Uuid).is_err()); + assert!(inet.validate_type(&D::Inet).is_ok()); + assert!(inet.validate_type(&D::Uuid).is_err()); assert!(date.validate_type(&D::Date).is_ok()); assert!(date.validate_type(&D::Text).is_err()); assert!(timestamp.validate_type(&D::Timestamp).is_ok()); @@ -1945,6 +1996,7 @@ mod tests { let map = Value::parse_json_map(r#"{ "a": 10 }"#).unwrap(); let list = Value::parse_json_list(r#"[ true ]"#).unwrap(); let bytea = Bytea(hex::decode("9001").unwrap()); + let inet = Inet(IpAddr::from_str("::1").unwrap()); assert_eq!(I8(1).get_type(), Some(D::Int8)); assert_eq!(I16(1).get_type(), Some(D::Int16)); @@ -1961,6 +2013,7 @@ mod tests { assert_eq!(Bool(true).get_type(), Some(D::Boolean)); assert_eq!(Str('1'.into()).get_type(), Some(D::Text)); assert_eq!(bytea.get_type(), Some(D::Bytea)); + assert_eq!(inet.get_type(), Some(D::Inet)); assert_eq!(date.get_type(), Some(D::Date)); assert_eq!(timestamp.get_type(), Some(D::Timestamp)); assert_eq!(time.get_type(), Some(D::Time)); diff --git a/core/src/data/value/string.rs b/core/src/data/value/string.rs new file mode 100644 index 000000000..73e687c1c --- /dev/null +++ b/core/src/data/value/string.rs @@ -0,0 +1,65 @@ +use {crate::data::Value, std::cmp::Ordering}; + +impl PartialEq for Value { + fn eq(&self, other: &str) -> bool { + match (self, other) { + (Value::Str(l), r) => l == r, + _ => false, + } + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &str) -> Option { + match (self, other) { + (Value::Str(l), r) => { + let l: &str = l.as_ref(); + Some(l.cmp(r)) + } + _ => None, + } + } +} + +impl> PartialEq for Value { + fn eq(&self, other: &T) -> bool { + PartialEq::::eq(self, other.as_ref()) + } +} + +impl> PartialOrd for Value { + fn partial_cmp(&self, other: &T) -> Option { + PartialOrd::::partial_cmp(self, other.as_ref()) + } +} + +#[cfg(test)] +mod tests { + use std::{borrow::Cow, cmp::Ordering}; + + use crate::{data::Literal, prelude::Value}; + + #[test] + fn eq() { + assert_eq!(Value::Str("wolf".to_owned()), "wolf"); + assert_eq!(Value::Str("wolf".to_owned()), "wolf".to_owned()); + assert_ne!(Value::I8(2), "2"); + + assert_eq!(Literal::Text(Cow::Borrowed("fox")), "fox"); + assert_eq!(Literal::Text(Cow::Borrowed("fox")), "fox".to_owned()); + assert_ne!(Literal::Boolean(true), "true"); + } + + #[test] + fn cmp() { + macro_rules! text { + ($text: expr) => { + Value::Str($text.to_owned()) + }; + } + assert_eq!(text!("wolf").partial_cmp("wolf"), Some(Ordering::Equal)); + assert_eq!(text!("apple").partial_cmp("wolf"), Some(Ordering::Less)); + assert_eq!(text!("zoo").partial_cmp("wolf"), Some(Ordering::Greater)); + assert_eq!(Value::I64(0).partial_cmp("0"), None); + } +} diff --git a/core/src/executor/aggregate/mod.rs b/core/src/executor/aggregate/mod.rs index 16bc28d6a..cc0f8c94b 100644 --- a/core/src/executor/aggregate/mod.rs +++ b/core/src/executor/aggregate/mod.rs @@ -21,8 +21,8 @@ use { pub use error::AggregateError; -pub struct Aggregator<'a> { - storage: &'a dyn GStore, +pub struct Aggregator<'a, T: GStore> { + storage: &'a T, fields: &'a [SelectItem], group_by: &'a [Expr], having: Option<&'a Expr>, @@ -35,9 +35,9 @@ enum Stream { Aggregate(T2), } -impl<'a> Aggregator<'a> { +impl<'a, T: GStore> Aggregator<'a, T> { pub fn new( - storage: &'a dyn GStore, + storage: &'a T, fields: &'a [SelectItem], group_by: &'a [Expr], having: Option<&'a Expr>, @@ -121,7 +121,7 @@ impl<'a> Aggregator<'a> { pub fn group_by_having( &self, - state: State<'a>, + state: State<'a, T>, ) -> Result< impl TryStream, Error = Error, Item = Result>>, > { @@ -190,11 +190,11 @@ impl<'a> Aggregator<'a> { } #[async_recursion(?Send)] -async fn aggregate<'a>( - state: State<'a>, +async fn aggregate<'a, T: GStore>( + state: State<'a, T>, filter_context: Option>>, expr: &'a Expr, -) -> Result> { +) -> Result> { let aggr = |state, expr| aggregate(state, filter_context.as_ref().map(Rc::clone), expr); match expr { diff --git a/core/src/executor/aggregate/state.rs b/core/src/executor/aggregate/state.rs index b6e75f319..e8a0c6607 100644 --- a/core/src/executor/aggregate/state.rs +++ b/core/src/executor/aggregate/state.rs @@ -149,24 +149,24 @@ impl AggrValue { } } -pub struct State<'a> { +pub struct State<'a, T: GStore> { + storage: &'a T, index: usize, group: Group, values: IndexMap<(Group, &'a Aggregate), (usize, AggrValue)>, groups: HashSet, contexts: Vector>>, - storage: &'a dyn GStore, } -impl<'a> State<'a> { - pub fn new(storage: &'a dyn GStore) -> Self { +impl<'a, T: GStore> State<'a, T> { + pub fn new(storage: &'a T) -> Self { State { + storage, index: 0, group: Rc::new(vec![Key::None]), values: IndexMap::new(), groups: HashSet::new(), contexts: Vector::new(), - storage, } } @@ -240,7 +240,7 @@ impl<'a> State<'a> { self, filter_context: Option>>, aggr: &'a Aggregate, - ) -> Result> { + ) -> Result> { let value = match aggr { Aggregate::Count(CountArgExpr::Wildcard) => Value::Null, Aggregate::Count(CountArgExpr::Expr(expr)) diff --git a/core/src/executor/alter/index.rs b/core/src/executor/alter/index.rs index d0a30beef..5d1bc683a 100644 --- a/core/src/executor/alter/index.rs +++ b/core/src/executor/alter/index.rs @@ -5,46 +5,34 @@ use { crate::{ ast::{ColumnDef, Expr, Function, OrderByExpr}, data::Schema, - result::MutResult, + result::Result, store::{GStore, GStoreMut}, }, }; pub async fn create_index( - storage: T, + storage: &mut T, table_name: &str, index_name: &str, column: &OrderByExpr, -) -> MutResult { - let names = (|| async { - let expr = &column.expr; - let Schema { column_defs, .. } = storage - .fetch_schema(table_name) - .await? - .ok_or_else(|| AlterError::TableNotFound(table_name.to_owned()))?; - let columns = column_defs - .unwrap_or_default() - .into_iter() - .map(|ColumnDef { name, .. }| name) - .collect::>(); - - let (valid, has_ident) = validate_index_expr(&columns, expr); - if !valid { - return Err(AlterError::UnsupportedIndexExpr(expr.clone()).into()); - } else if !has_ident { - return Err(AlterError::IdentifierNotFound(expr.clone()).into()); - } - - Ok((table_name, index_name)) - })() - .await; - - let (table_name, index_name) = match names { - Ok(s) => s, - Err(e) => { - return Err((storage, e)); - } - }; +) -> Result<()> { + let expr = &column.expr; + let Schema { column_defs, .. } = storage + .fetch_schema(table_name) + .await? + .ok_or_else(|| AlterError::TableNotFound(table_name.to_owned()))?; + let columns = column_defs + .unwrap_or_default() + .into_iter() + .map(|ColumnDef { name, .. }| name) + .collect::>(); + + let (valid, has_ident) = validate_index_expr(&columns, expr); + if !valid { + return Err(AlterError::UnsupportedIndexExpr(expr.clone()).into()); + } else if !has_ident { + return Err(AlterError::IdentifierNotFound(expr.clone()).into()); + } storage.create_index(table_name, index_name, column).await } diff --git a/core/src/executor/alter/table.rs b/core/src/executor/alter/table.rs index 5e5ed0894..ef8b1a6f3 100644 --- a/core/src/executor/alter/table.rs +++ b/core/src/executor/alter/table.rs @@ -5,11 +5,11 @@ use { data::{Schema, TableError}, executor::{evaluate_stateless, select::select}, prelude::{DataType, Value}, - result::{Error, IntoControlFlow, MutResult, Result, TrySelf}, + result::{Error, IntoControlFlow, Result}, store::{GStore, GStoreMut}, }, chrono::Utc, - futures::stream::{self, TryStreamExt}, + futures::stream::TryStreamExt, std::{ iter, ops::ControlFlow::{Break, Continue}, @@ -17,172 +17,148 @@ use { }; pub async fn create_table( - storage: T, + storage: &mut T, target_table_name: &str, - column_defs: &[ColumnDef], + column_defs: Option<&[ColumnDef]>, if_not_exists: bool, source: &Option>, -) -> MutResult { - let schema = (|| async { - let target_columns_defs = match source.as_ref().map(AsRef::as_ref) { - Some(Query { body, .. }) => match body { - SetExpr::Select(select_query) => match &select_query.from.relation { - TableFactor::Table { name, .. } => { - let schema = storage.fetch_schema(name).await?; - let Schema { - column_defs: source_column_defs, - .. - } = schema.ok_or_else(|| -> Error { - AlterError::CtasSourceTableNotFound(name.to_owned()).into() - })?; - - source_column_defs - } - TableFactor::Series { .. } => { - let column_def = ColumnDef { - name: "N".into(), - data_type: DataType::Int, - nullable: false, - default: None, - unique: None, - }; - - Some(vec![column_def]) - } - _ => { - return Err(Error::Table(TableError::Unreachable)); - } - }, - SetExpr::Values(Values(values_list)) => { - let first_len = values_list[0].len(); - let init_types = iter::repeat(None) - .take(first_len) - .collect::>>(); - let column_types = - values_list - .iter() - .try_fold(init_types, |column_types, exprs| { - let column_types = column_types - .iter() - .zip(exprs.iter()) - .map(|(column_type, expr)| match column_type { - Some(data_type) => Ok(Some(data_type.to_owned())), - None => evaluate_stateless(None, expr) - .and_then(Value::try_from) - .map(|value| value.get_type()), - }) - .collect::>>>() - .into_control_flow()?; - - match column_types.iter().any(Option::is_none) { - true => Continue(column_types), - false => Break(Ok(column_types)), - } - }); - let column_types = match column_types { - Continue(column_types) => column_types, - Break(column_types) => column_types?, + engine: &Option, +) -> Result<()> { + let target_columns_defs = match source.as_deref() { + Some(Query { body, .. }) => match body { + SetExpr::Select(select_query) => match &select_query.from.relation { + TableFactor::Table { name, .. } => { + let schema = storage.fetch_schema(name).await?; + let Schema { + column_defs: source_column_defs, + .. + } = schema.ok_or_else(|| -> Error { + AlterError::CtasSourceTableNotFound(name.to_owned()).into() + })?; + + source_column_defs + } + TableFactor::Series { .. } => { + let column_def = ColumnDef { + name: "N".into(), + data_type: DataType::Int, + nullable: false, + default: None, + unique: None, }; - let column_defs = column_types - .iter() - .map(|column_type| match column_type { - Some(column_type) => column_type.to_owned(), - None => DataType::Text, - }) - .enumerate() - .map(|(i, data_type)| ColumnDef { - name: format!("column{}", i + 1), - data_type, - nullable: true, - default: None, - unique: None, - }) - .collect::>(); - Some(column_defs) + Some(vec![column_def]) + } + _ => { + return Err(Error::Table(TableError::Unreachable)); } }, - None if !column_defs.is_empty() => Some(column_defs.to_vec()), - None => None, - }; + SetExpr::Values(Values(values_list)) => { + let first_len = values_list[0].len(); + let init_types = iter::repeat(None) + .take(first_len) + .collect::>>(); + let column_types = + values_list + .iter() + .try_fold(init_types, |column_types, exprs| { + let column_types = column_types + .iter() + .zip(exprs.iter()) + .map(|(column_type, expr)| match column_type { + Some(data_type) => Ok(Some(data_type.to_owned())), + None => evaluate_stateless(None, expr) + .and_then(Value::try_from) + .map(|value| value.get_type()), + }) + .collect::>>>() + .into_control_flow()?; + + match column_types.iter().any(Option::is_none) { + true => Continue(column_types), + false => Break(Ok(column_types)), + } + }); + let column_types = match column_types { + Continue(column_types) => column_types, + Break(column_types) => column_types?, + }; + let column_defs = column_types + .iter() + .map(|column_type| match column_type { + Some(column_type) => column_type.to_owned(), + None => DataType::Text, + }) + .enumerate() + .map(|(i, data_type)| ColumnDef { + name: format!("column{}", i + 1), + data_type, + nullable: true, + default: None, + unique: None, + }) + .collect::>(); + + Some(column_defs) + } + }, + None if column_defs.is_some() => column_defs.map(<[ColumnDef]>::to_vec), + None => None, + }; + if let Some(column_defs) = target_columns_defs.as_deref() { + validate_column_names(column_defs)?; + + for column_def in column_defs { + validate(column_def)?; + } + } + + if storage.fetch_schema(target_table_name).await?.is_none() { let schema = Schema { table_name: target_table_name.to_owned(), column_defs: target_columns_defs, indexes: vec![], + engine: engine.clone(), created: Utc::now().naive_utc(), }; - if let Some(column_defs) = schema.column_defs.as_deref() { - validate_column_names(column_defs)?; - - for column_def in column_defs { - validate(column_def)?; - } - } - - match ( - storage.fetch_schema(&schema.table_name).await?, - if_not_exists, - ) { - (None, _) => Ok(Some(schema)), - (Some(_), true) => Ok(None), - (Some(_), false) => { - Err(AlterError::TableAlreadyExists(schema.table_name.to_owned()).into()) - } - } - })() - .await; - - let storage = match schema.try_self(storage)? { - (storage, Some(schema)) => storage.insert_schema(&schema).await?.0, - (storage, None) => storage, - }; + storage.insert_schema(&schema).await?; + } else if !if_not_exists { + return Err(AlterError::TableAlreadyExists(target_table_name.to_owned()).into()); + } match source { - Some(q) => { - let (storage, rows) = async { - select(&storage, q, None) - .await? - .map_ok(Into::into) - .try_collect() - .await - } - .await - .try_self(storage)?; - - storage.append_data(target_table_name, rows).await + Some(query) => { + let rows = select(storage, query, None) + .await? + .map_ok(Into::into) + .try_collect() + .await?; + + storage + .append_data(target_table_name, rows) + .await + .map(|_| ()) } - None => Ok((storage, ())), + None => Ok(()), } } pub async fn drop_table( - storage: T, + storage: &mut T, table_names: &[String], if_exists: bool, -) -> MutResult { - stream::iter(table_names.iter().map(Ok)) - .try_fold((storage, ()), |(storage, _), table_name| async move { - let schema = (|| async { - let schema = storage.fetch_schema(table_name).await?; - - if !if_exists { - schema.ok_or_else(|| AlterError::TableNotFound(table_name.to_owned()))?; - } +) -> Result<()> { + for table_name in table_names { + let schema = storage.fetch_schema(table_name).await?; - Ok(table_name) - })() - .await; + if !if_exists { + schema.ok_or_else(|| AlterError::TableNotFound(table_name.to_owned()))?; + } - let schema = match schema { - Ok(s) => s, - Err(e) => { - return Err((storage, e)); - } - }; + storage.delete_schema(table_name).await?; + } - storage.delete_schema(schema).await - }) - .await + Ok(()) } diff --git a/core/src/executor/evaluate/error.rs b/core/src/executor/evaluate/error.rs index f64fdf761..93a68c5bc 100644 --- a/core/src/executor/evaluate/error.rs +++ b/core/src/executor/evaluate/error.rs @@ -85,6 +85,18 @@ pub enum EvaluateError { #[error("function requires integer value in range")] ChrFunctionRequiresIntegerValueInRange0To255, + + #[error("unsupported evaluate binary arithmetic between {0} and {1}")] + UnsupportedBinaryArithmetic(String, String), + + #[error("unsupported evaluate string unary plus: {0}")] + UnsupportedUnaryPlus(String), + + #[error("unsupported evaluate string unary minus: {0}")] + UnsupportedUnaryMinus(String), + + #[error("unsupported evaluate string unary factorial: {0}")] + UnsupportedUnaryFactorial(String), } fn error_serialize(error: &chrono::format::ParseError, serializer: S) -> Result diff --git a/core/src/executor/evaluate/evaluated.rs b/core/src/executor/evaluate/evaluated.rs index 3ee0ce4ae..0161d83ef 100644 --- a/core/src/executor/evaluate/evaluated.rs +++ b/core/src/executor/evaluate/evaluated.rs @@ -1,16 +1,20 @@ use { super::error::EvaluateError, crate::{ - ast::DataType, + ast::{DataType, TrimWhereField}, data::{value::HashMapJsonExt, Key, Literal, Value}, result::{Error, Result}, }, - std::{cmp::Ordering, collections::HashMap}, + std::{borrow::Cow, cmp::Ordering, collections::HashMap, ops::Range}, }; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Evaluated<'a> { Literal(Literal<'a>), + StrSlice { + source: Cow<'a, str>, + range: Range, + }, Value(Value), } @@ -26,6 +30,10 @@ impl TryFrom> for Value { fn try_from(e: Evaluated<'_>) -> Result { match e { Evaluated::Literal(v) => Value::try_from(v), + Evaluated::StrSlice { + source: s, + range: r, + } => Ok(Value::Str(s[r].to_owned())), Evaluated::Value(v) => Ok(v), } } @@ -45,6 +53,7 @@ impl TryFrom<&Evaluated<'_>> for Key { fn try_from(evaluated: &Evaluated<'_>) -> Result { match evaluated { Evaluated::Literal(l) => Value::try_from(l)?.try_into(), + Evaluated::StrSlice { source, range } => Ok(Key::Str(source[range.clone()].to_owned())), Evaluated::Value(v) => v.try_into(), } } @@ -59,6 +68,9 @@ impl TryFrom> for bool { Evaluated::Literal(v) => { Err(EvaluateError::BooleanTypeRequired(format!("{:?}", v)).into()) } + Evaluated::StrSlice { source, range } => { + Err(EvaluateError::BooleanTypeRequired(source[range].to_owned()).into()) + } Evaluated::Value(Value::Bool(v)) => Ok(v), Evaluated::Value(v) => { Err(EvaluateError::BooleanTypeRequired(format!("{:?}", v)).into()) @@ -79,6 +91,7 @@ impl TryFrom> for HashMap { Evaluated::Value(Value::Str(v)) => HashMap::parse_json_object(v.as_str()), Evaluated::Value(Value::Map(v)) => Ok(v), Evaluated::Value(v) => Err(EvaluateError::MapOrStringValueRequired(v.into()).into()), + Evaluated::StrSlice { source, range } => HashMap::parse_json_object(&source[range]), } } } @@ -90,6 +103,21 @@ impl<'a> PartialEq for Evaluated<'a> { (Evaluated::Literal(b), Evaluated::Value(a)) | (Evaluated::Value(a), Evaluated::Literal(b)) => a == b, (Evaluated::Value(a), Evaluated::Value(b)) => a == b, + (Evaluated::Literal(a), Evaluated::StrSlice { source, range }) + | (Evaluated::StrSlice { source, range }, Evaluated::Literal(a)) => { + a == &source[range.clone()] + } + (Evaluated::Value(a), Evaluated::StrSlice { source, range }) + | (Evaluated::StrSlice { source, range }, Evaluated::Value(a)) => { + a == &source[range.clone()] + } + ( + Evaluated::StrSlice { source, range }, + Evaluated::StrSlice { + source: source2, + range: range2, + }, + ) => source[range.clone()] == source2[range2.clone()], } } } @@ -101,6 +129,28 @@ impl<'a> PartialOrd for Evaluated<'a> { (Evaluated::Literal(l), Evaluated::Value(r)) => r.partial_cmp(l).map(|o| o.reverse()), (Evaluated::Value(l), Evaluated::Literal(r)) => l.partial_cmp(r), (Evaluated::Value(l), Evaluated::Value(r)) => l.partial_cmp(r), + (Evaluated::Literal(l), Evaluated::StrSlice { source, range }) => { + l.partial_cmp(&source[range.clone()]) + } + (Evaluated::Value(l), Evaluated::StrSlice { source, range }) => { + l.partial_cmp(&source[range.clone()]) + } + (Evaluated::StrSlice { source, range }, Evaluated::Literal(l)) => { + l.partial_cmp(&source[range.clone()]).map(|o| o.reverse()) + } + (Evaluated::StrSlice { source, range }, Evaluated::Value(r)) => { + r.partial_cmp(&source[range.clone()]).map(|o| o.reverse()) + } + ( + Evaluated::StrSlice { + source: a, + range: ar, + }, + Evaluated::StrSlice { + source: b, + range: br, + }, + ) => a[ar.clone()].partial_cmp(&b[br.clone()]), } } } @@ -124,6 +174,18 @@ where value_op(l, &Value::try_from(r)?).map(Evaluated::from) } (Evaluated::Value(l), Evaluated::Value(r)) => value_op(l, r).map(Evaluated::from), + (l, r) => Err(EvaluateError::UnsupportedBinaryArithmetic( + format!("{:?}", l), + format!("{:?}", r), + ) + .into()), + } +} + +pub fn exceptional_int_val_to_eval<'a>(name: String, v: Value) -> Result> { + match v { + Value::Null => Ok(Evaluated::from(Value::Null)), + _ => Err(EvaluateError::FunctionRequiresIntegerValue(name).into()), } } @@ -152,6 +214,9 @@ impl<'a> Evaluated<'a> { match self { Evaluated::Literal(v) => v.unary_plus().map(Evaluated::Literal), Evaluated::Value(v) => v.unary_plus().map(Evaluated::from), + Evaluated::StrSlice { source, range } => { + Err(EvaluateError::UnsupportedUnaryPlus(source[range.clone()].to_owned()).into()) + } } } @@ -159,6 +224,9 @@ impl<'a> Evaluated<'a> { match self { Evaluated::Literal(v) => v.unary_minus().map(Evaluated::Literal), Evaluated::Value(v) => v.unary_minus().map(Evaluated::from), + Evaluated::StrSlice { source, range } => { + Err(EvaluateError::UnsupportedUnaryMinus(source[range.clone()].to_owned()).into()) + } } } @@ -166,6 +234,10 @@ impl<'a> Evaluated<'a> { match self { Evaluated::Literal(v) => Value::try_from(v).and_then(|v| v.unary_factorial()), Evaluated::Value(v) => v.unary_factorial(), + Evaluated::StrSlice { source, range } => Err(EvaluateError::UnsupportedUnaryFactorial( + source[range.clone()].to_owned(), + ) + .into()), } .map(Evaluated::from) } @@ -177,6 +249,9 @@ impl<'a> Evaluated<'a> { match self { Evaluated::Literal(value) => cast_literal(&value), Evaluated::Value(value) => cast_value(&value), + Evaluated::StrSlice { source, range } => { + cast_value(&Value::Str(source[range].to_owned())) + } } .map(Evaluated::from) } @@ -191,6 +266,28 @@ impl<'a> Evaluated<'a> { Evaluated::from(l.concat(Value::try_from(r)?)) } (Evaluated::Value(l), Evaluated::Value(r)) => Evaluated::from(l.concat(r)), + (Evaluated::Literal(l), Evaluated::StrSlice { source, range }) => { + Evaluated::from((Value::try_from(l)?).concat(Value::Str(source[range].to_owned()))) + } + (Evaluated::Value(l), Evaluated::StrSlice { source, range }) => { + Evaluated::from(l.concat(Value::Str(source[range].to_owned()))) + } + (Evaluated::StrSlice { source, range }, Evaluated::Literal(r)) => { + Evaluated::from(Value::Str(source[range].to_owned()).concat(Value::try_from(r)?)) + } + (Evaluated::StrSlice { source, range }, Evaluated::Value(r)) => { + Evaluated::from(Value::Str(source[range].to_owned()).concat(r)) + } + ( + Evaluated::StrSlice { + source: a, + range: ar, + }, + Evaluated::StrSlice { + source: b, + range: br, + }, + ) => Evaluated::from(Value::Str(a[ar].to_owned()).concat(Value::Str(b[br].to_owned()))), }; Ok(evaluated) @@ -210,22 +307,444 @@ impl<'a> Evaluated<'a> { (Evaluated::Value(l), Evaluated::Value(r)) => { Evaluated::from(l.like(&r, case_sensitive)?) } + (Evaluated::Literal(l), Evaluated::StrSlice { source, range }) => Evaluated::from( + Value::try_from(l)?.like(&Value::Str(source[range].to_owned()), case_sensitive)?, + ), + (Evaluated::StrSlice { source, range }, Evaluated::Literal(r)) => Evaluated::from( + Value::Str(source[range.clone()].to_owned()) + .like(&Value::try_from(r)?, case_sensitive)?, + ), + ( + Evaluated::StrSlice { + source: a, + range: ar, + }, + Evaluated::StrSlice { + source: b, + range: br, + }, + ) => Evaluated::from( + Value::Str(a[ar.clone()].to_owned()) + .like(&Value::Str(b[br].to_owned()), case_sensitive)?, + ), + (Evaluated::StrSlice { source, range }, Evaluated::Value(r)) => Evaluated::from( + Value::Str(source[range.clone()].to_owned()).like(&r, case_sensitive)?, + ), + (Evaluated::Value(l), Evaluated::StrSlice { source, range }) => { + Evaluated::from(l.like(&Value::Str(source[range].to_owned()), case_sensitive)?) + } }; Ok(evaluated) } + pub fn ltrim(self, name: String, chars: Option>) -> Result> { + let (source, range) = match self { + Evaluated::Literal(Literal::Text(l)) => { + let end = l.len(); + (l, 0..end) + } + Evaluated::Literal(Literal::Null) | Evaluated::Value(Value::Null) => { + return Ok(Evaluated::from(Value::Null)) + } + Evaluated::StrSlice { source, range } => (source, range), + Evaluated::Value(Value::Str(v)) => { + let end = v.len(); + (Cow::Owned(v), 0..end) + } + _ => return Err(EvaluateError::FunctionRequiresStringValue(name).into()), + }; + + let filter_chars = match chars { + Some(expr) => match expr.try_into()? { + Value::Str(value) => value, + Value::Null => { + return Ok(Evaluated::from(Value::Null)); + } + _ => { + return Err(EvaluateError::FunctionRequiresStringValue(name).into()); + } + } + .chars() + .collect::>(), + None => vec![' '], + }; + let sliced_expr = &source[range.clone()]; + let matched_vec: Vec<_> = sliced_expr.match_indices(&filter_chars[..]).collect(); + + //"x".trim_start_matches(['x','y','z']) => "" + if matched_vec.len() == sliced_expr.len() { + return Ok(Evaluated::StrSlice { + source, + range: 0..0, + }); + } + //"tuv".trim_start_matches(['x','y','z']) => "tuv" + if matched_vec.is_empty() { + return Ok(Evaluated::StrSlice { source, range }); + } + //"txu".trim_start_matches(['x','y','z']) => "txu" + if matched_vec[0].0 != 0 && matched_vec[matched_vec.len() - 1].0 != sliced_expr.len() - 1 { + return Ok(Evaluated::StrSlice { source, range }); + } + let pivot = matched_vec + .iter() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| vec_idx == slice_idx) + .map(|(vec_idx, (_, _))| vec_idx) + .next(); + + let start = match pivot { + Some(idx) => match idx { + 0 => 0, + _ => matched_vec[idx - 1].0 + 1, + }, + _ => matched_vec[matched_vec.len() - 1].0 + 1, + }; + + Ok(Evaluated::StrSlice { + source, + range: range.start + start..range.end, + }) + } + pub fn is_null(&self) -> bool { match self { Evaluated::Value(v) => v.is_null(), + Evaluated::StrSlice { .. } => false, Evaluated::Literal(v) => matches!(v, &Literal::Null), } } + pub fn rtrim(self, name: String, chars: Option>) -> Result> { + let (source, range) = match self { + Evaluated::Literal(Literal::Text(l)) => { + let end = l.len(); + (l, 0..end) + } + Evaluated::Literal(Literal::Null) | Evaluated::Value(Value::Null) => { + return Ok(Evaluated::from(Value::Null)) + } + Evaluated::StrSlice { source, range } => (source, range), + Evaluated::Value(Value::Str(v)) => { + let end = v.len(); + (Cow::Owned(v), 0..end) + } + _ => return Err(EvaluateError::FunctionRequiresStringValue(name).into()), + }; + + let filter_chars = match chars { + Some(expr) => match expr.try_into()? { + Value::Str(value) => value, + Value::Null => { + return Ok(Evaluated::from(Value::Null)); + } + _ => { + return Err(EvaluateError::FunctionRequiresStringValue(name).into()); + } + } + .chars() + .collect::>(), + None => vec![' '], + }; + let sliced_expr = &source[range.clone()]; + let matched_vec: Vec<_> = sliced_expr.match_indices(&filter_chars[..]).collect(); + + //"x".trim_end_matches(['x','y','z']) => "" + if matched_vec.len() == sliced_expr.len() { + return Ok(Evaluated::StrSlice { + source, + range: 0..0, + }); + } + //"tuv".trim_end_matches(['x','y','z']) => "tuv" + if matched_vec.is_empty() { + return Ok(Evaluated::StrSlice { source, range }); + } + //"txu".trim_end_matches(['x','y','z']) => "txu" + if matched_vec[0].0 != 0 && matched_vec[matched_vec.len() - 1].0 != sliced_expr.len() - 1 { + return Ok(Evaluated::StrSlice { source, range }); + } + + let pivot = matched_vec + .iter() + .rev() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| *vec_idx == sliced_expr.len() - slice_idx - 1) + .map(|(vec_idx, (_, _))| vec_idx) + .next(); + + let end = match pivot { + Some(idx) => match idx { + 0 => range.end, + _ => matched_vec[matched_vec.len() - idx].0, + }, + _ => matched_vec[0].0, + }; + + Ok(Evaluated::StrSlice { + source, + range: range.start..end, + }) + } + + pub fn substr( + self, + name: String, + start: Evaluated<'a>, + count: Option>, + ) -> Result> { + let (source, range) = match self { + Evaluated::Literal(Literal::Text(l)) => { + let end = l.len(); + (l, 0..end) + } + Evaluated::Literal(Literal::Null) | Evaluated::Value(Value::Null) => { + return Ok(Evaluated::from(Value::Null)) + } + Evaluated::StrSlice { source, range } => (source, range), + Evaluated::Value(Value::Str(v)) => { + let end = v.len(); + (Cow::Owned(v), 0..end) + } + _ => return Err(EvaluateError::FunctionRequiresStringValue(name).into()), + }; + + let start = { + let value = start.try_into()?; + match value { + Value::I64(num) => num, + _ => return exceptional_int_val_to_eval(name, value), + } + } - 1; + + let count = match count { + Some(eval) => { + let value = eval.try_into()?; + match value { + Value::I64(num) => num, + _ => return exceptional_int_val_to_eval(name, value), + } + } + None => source.len() as i64, + }; + + let end = if count < 0 { + return Err(EvaluateError::NegativeSubstrLenNotAllowed.into()); + } else { + (range.start as i64 + start + count).clamp(0, source.len() as i64) as usize + }; + + let start = (start + range.start as i64).clamp(0, source.len() as i64) as usize; + + Ok(Evaluated::StrSlice { + source, + range: start..end, + }) + } + + pub fn trim( + self, + name: String, + filter_chars: Option>, + trim_where_field: &Option, + ) -> Result> { + let (source, range) = match self { + Evaluated::Literal(Literal::Text(l)) => { + let end = l.len(); + (l, 0..end) + } + Evaluated::Literal(Literal::Null) | Evaluated::Value(Value::Null) => { + return Ok(Evaluated::from(Value::Null)) + } + Evaluated::StrSlice { source, range } => (source, range), + Evaluated::Value(Value::Str(v)) => { + let end = v.len(); + (Cow::Owned(v), 0..end) + } + _ => return Err(EvaluateError::FunctionRequiresStringValue(name).into()), + }; + + let filter_chars = match filter_chars { + Some(expr) => match expr.try_into()? { + Value::Str(value) => value, + Value::Null => { + return Ok(Evaluated::from(Value::Null)); + } + _ => { + return Err(EvaluateError::FunctionRequiresStringValue(name).into()); + } + } + .chars() + .collect::>(), + None => vec![' '], + }; + let sliced_expr = &source[range.clone()]; + let matched_vec: Vec<_> = sliced_expr.match_indices(&filter_chars[..]).collect(); + //filter_chars => ['x','y','z'] + //"x".trim_matches(filter_chars[..]) => "" + if matched_vec.len() == sliced_expr.len() { + return Ok(Evaluated::StrSlice { + source, + range: 0..0, + }); + } + //filter_chars => ['x','y','z'] + //"tuv".trim_matches(filter_chars[..]) => "tuv" + if matched_vec.is_empty() { + return Ok(Evaluated::StrSlice { source, range }); + } + //filter_chars => ['x','y','z'] + //"txu".trim_matches(filter_chars[..]) => "txu" + if matched_vec[0].0 != 0 && matched_vec[matched_vec.len() - 1].0 != sliced_expr.len() - 1 { + return Ok(Evaluated::StrSlice { source, range }); + } + match trim_where_field { + Some(TrimWhereField::Both) => { + //filter_chars => ['x','y','z'] + //"xyzbyxlxyz ".trim_matches(filter_chars[..]) => "byxlxyz " + if matched_vec[0].0 == 0 + && matched_vec[matched_vec.len() - 1].0 != sliced_expr.len() - 1 + { + let pivot = matched_vec + .iter() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| vec_idx == slice_idx) + .map(|(vec_idx, (_, _))| vec_idx) + .next(); + + let start = match pivot { + Some(idx) => matched_vec[idx - 1].0 + 1, + _ => matched_vec[matched_vec.len() - 1].0 + 1, + }; + + return Ok(Evaluated::StrSlice { + source, + range: range.start + start..range.end, + }); + } + //filter_chars => ['x','y','z'] + //" xyzblankxyzxx".trim_matches(filter_chars[..]) => " xyzblank" + if matched_vec[0].0 != 0 + && matched_vec[matched_vec.len() - 1].0 == sliced_expr.len() - 1 + { + let pivot = matched_vec + .iter() + .rev() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| { + *vec_idx == sliced_expr.len() - slice_idx - 1 + }) + .map(|(vec_idx, (_, _))| vec_idx) + .next(); + + let end = match pivot { + Some(idx) => matched_vec[matched_vec.len() - idx].0, + _ => matched_vec[0].0, + }; + + return Ok(Evaluated::StrSlice { + source, + range: range.start..end, + }); + } + //filter_chars => ['x','y','z'] + //"xxbyz".trim_matches(filter_chars[..]) => "b" + let pivot = matched_vec + .iter() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| vec_idx == slice_idx) + .map(|(vec_idx, (_, _))| vec_idx) + .next() + .unwrap_or(0); + + let trim_range = matched_vec[pivot - 1].0..(matched_vec[pivot].0 + range.start); + + Ok(Evaluated::StrSlice { + source, + range: range.start + trim_range.start + 1..trim_range.end, + }) + } + Some(TrimWhereField::Leading) => { + let pivot = matched_vec + .iter() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| vec_idx == slice_idx) + .map(|(vec_idx, (_, _))| vec_idx) + .next(); + + let start = match pivot { + Some(idx) => match idx { + 0 => 0, + _ => matched_vec[idx - 1].0 + 1, + }, + _ => matched_vec[matched_vec.len() - 1].0 + 1, + }; + + Ok(Evaluated::StrSlice { + source, + range: range.start + start..range.end, + }) + } + Some(TrimWhereField::Trailing) => { + let pivot = matched_vec + .iter() + .rev() + .enumerate() + .skip_while(|(vec_idx, (slice_idx, _))| { + *vec_idx == sliced_expr.len() - slice_idx - 1 + }) + .map(|(vec_idx, (_, _))| vec_idx) + .next(); + + let end = match pivot { + Some(idx) => match idx { + 0 => range.end, + _ => matched_vec[matched_vec.len() - idx].0, + }, + _ => matched_vec[0].0, + }; + + Ok(Evaluated::StrSlice { + source, + range: range.start..end, + }) + } + None => { + let start = source + .chars() + .skip(range.start) + .enumerate() + .find(|(_, c)| !c.is_whitespace()) + .map(|(idx, _)| idx + range.start) + .unwrap_or(0); + + let end = source.len() + - source + .chars() + .rev() + .skip(source.len() - range.end) + .enumerate() + .find(|(_, c)| !c.is_whitespace()) + .map(|(idx, _)| source.len() - (range.end - idx)) + .unwrap_or(0); + + Ok(Evaluated::StrSlice { + source, + range: start..end, + }) + } + } + } + pub fn try_into_value(self, data_type: &DataType, nullable: bool) -> Result { let value = match self { - Evaluated::Value(v) => v, Evaluated::Literal(v) => Value::try_from_literal(data_type, &v)?, + Evaluated::Value(v) => v, + Evaluated::StrSlice { + source: s, + range: r, + } => Value::Str(s[r].to_owned()), }; value.validate_null(nullable)?; diff --git a/core/src/executor/evaluate/expr.rs b/core/src/executor/evaluate/expr.rs index 2f5330ef4..6e711390e 100644 --- a/core/src/executor/evaluate/expr.rs +++ b/core/src/executor/evaluate/expr.rs @@ -12,7 +12,7 @@ pub fn literal(ast_literal: &AstLiteral) -> Result> { Literal::try_from(ast_literal).map(Evaluated::Literal) } -pub fn typed_string<'a>(data_type: &'a DataType, value: Cow<'a, String>) -> Result> { +pub fn typed_string<'a>(data_type: &'a DataType, value: Cow<'a, str>) -> Result> { let literal = Literal::Text(value); Value::try_from_literal(data_type, &literal).map(Evaluated::from) diff --git a/core/src/executor/evaluate/function.rs b/core/src/executor/evaluate/function.rs index f0093bad2..0d94c9d61 100644 --- a/core/src/executor/evaluate/function.rs +++ b/core/src/executor/evaluate/function.rs @@ -1,14 +1,12 @@ use { super::{EvaluateError, Evaluated}, crate::{ - ast::{DataType, DateTimeField, TrimWhereField}, + ast::{DataType, DateTimeField}, data::{Value, ValueError}, result::Result, }, - std::{ - cmp::{max, min}, - ops::ControlFlow, - }, + rand::{rngs::StdRng, Rng, SeedableRng}, + std::ops::ControlFlow, uuid::Uuid, }; @@ -187,59 +185,6 @@ pub fn lpad_or_rpad<'a>( Ok(Evaluated::from(Value::Str(result))) } -pub fn trim<'a>( - name: String, - expr: Evaluated<'_>, - filter_chars: Option>, - trim_where_field: &'a Option, -) -> Result> { - let expr_str = eval_to_str!(name, expr); - let expr_str = expr_str.as_str(); - let filter_chars = match filter_chars { - Some(expr) => eval_to_str!(name, expr).chars().collect::>(), - None => vec![' '], - }; - - let value = match trim_where_field { - Some(TrimWhereField::Both) => expr_str.trim_matches(&filter_chars[..]), - Some(TrimWhereField::Leading) => expr_str.trim_start_matches(&filter_chars[..]), - Some(TrimWhereField::Trailing) => expr_str.trim_end_matches(&filter_chars[..]), - None => expr_str.trim(), - }; - - Ok(Evaluated::from(Value::Str(value.to_owned()))) -} - -pub fn ltrim<'a>( - name: String, - expr: Evaluated<'_>, - chars: Option>, -) -> Result> { - let expr = eval_to_str!(name, expr); - let chars = match chars { - Some(chars) => eval_to_str!(name, chars).chars().collect::>(), - None => vec![' '], - }; - - let value = expr.trim_start_matches(chars.as_slice()).to_owned(); - Ok(Evaluated::from(Value::Str(value))) -} - -pub fn rtrim<'a>( - name: String, - expr: Evaluated<'_>, - chars: Option>, -) -> Result> { - let expr = eval_to_str!(name, expr); - let chars = match chars { - Some(chars) => eval_to_str!(name, chars).chars().collect::>(), - None => vec![' '], - }; - - let value = expr.trim_end_matches(chars.as_slice()).to_owned(); - Ok(Evaluated::from(Value::Str(value))) -} - pub fn reverse(name: String, expr: Evaluated<'_>) -> Result { let value = eval_to_str!(name, expr).chars().rev().collect::(); @@ -254,30 +199,6 @@ pub fn repeat<'a>(name: String, expr: Evaluated<'_>, num: Evaluated<'_>) -> Resu Ok(Evaluated::from(Value::Str(value))) } -pub fn substr<'a>( - name: String, - expr: Evaluated<'_>, - start: Evaluated<'_>, - count: Option>, -) -> Result> { - let string = eval_to_str!(name, expr); - let start = eval_to_int!(name, start) - 1; - let count = match count { - Some(v) => eval_to_int!(name, v), - None => string.len() as i64, - }; - - let end = if count < 0 { - return Err(EvaluateError::NegativeSubstrLenNotAllowed.into()); - } else { - min(max(start + count, 0) as usize, string.len()) - }; - - let start = min(max(start, 0) as usize, string.len()); - let string = String::from(&string[start..end]); - Ok(Evaluated::from(Value::Str(string))) -} - pub fn ascii<'a>(name: String, expr: Evaluated<'_>) -> Result> { let string = eval_to_str!(name, expr); let mut iter = string.chars(); @@ -349,6 +270,15 @@ pub fn ceil<'a>(name: String, n: Evaluated<'_>) -> Result> { Ok(Evaluated::from(Value::F64(eval_to_float!(name, n).ceil()))) } +pub fn rand<'a>(name: String, seed: Option>) -> Result> { + let seed = if let Some(v) = seed { + StdRng::seed_from_u64(eval_to_float!(name, v) as u64).gen() + } else { + rand::random() + }; + Ok(Evaluated::from(Value::F64(seed))) +} + pub fn round<'a>(name: String, n: Evaluated<'_>) -> Result> { Ok(Evaluated::from(Value::F64(eval_to_float!(name, n).round()))) } diff --git a/core/src/executor/evaluate/mod.rs b/core/src/executor/evaluate/mod.rs index 2df94b93f..e8de5f7b2 100644 --- a/core/src/executor/evaluate/mod.rs +++ b/core/src/executor/evaluate/mod.rs @@ -25,8 +25,8 @@ use { pub use {error::EvaluateError, evaluated::Evaluated, stateless::evaluate_stateless}; #[async_recursion(?Send)] -pub async fn evaluate<'a, 'b: 'a, 'c: 'a>( - storage: &'a dyn GStore, +pub async fn evaluate<'a, 'b: 'a, 'c: 'a, T: GStore>( + storage: &'a T, context: Option>>, aggregated: Option>>, expr: &'a Expr, @@ -271,8 +271,8 @@ pub async fn evaluate<'a, 'b: 'a, 'c: 'a>( } } -async fn evaluate_function<'a, 'b: 'a, 'c: 'a>( - storage: &'a dyn GStore, +async fn evaluate_function<'a, 'b: 'a, 'c: 'a, T: GStore>( + storage: &'a T, context: Option>>, aggregated: Option>>, func: &'b Function, @@ -329,7 +329,7 @@ async fn evaluate_function<'a, 'b: 'a, 'c: 'a>( None => None, }; - f::trim(name, expr, filter_chars, trim_where_field) + expr.trim(name, filter_chars, trim_where_field) } Function::Ltrim { expr, chars } => { let expr = eval(expr).await?; @@ -338,7 +338,7 @@ async fn evaluate_function<'a, 'b: 'a, 'c: 'a>( None => None, }; - f::ltrim(name, expr, chars) + expr.ltrim(name, chars) } Function::Rtrim { expr, chars } => { let expr = eval(expr).await?; @@ -347,7 +347,7 @@ async fn evaluate_function<'a, 'b: 'a, 'c: 'a>( None => None, }; - f::rtrim(name, expr, chars) + expr.rtrim(name, chars) } Function::Reverse(expr) => { let expr = eval(expr).await?; @@ -367,8 +367,7 @@ async fn evaluate_function<'a, 'b: 'a, 'c: 'a>( Some(v) => Some(eval(v).await?), None => None, }; - - f::substr(name, expr, start, count) + expr.substr(name, start, count) } Function::Ascii(expr) => f::ascii(name, eval(expr).await?), Function::Chr(expr) => f::chr(name, eval(expr).await?), @@ -384,6 +383,13 @@ async fn evaluate_function<'a, 'b: 'a, 'c: 'a>( f::power(name, expr, power) } Function::Ceil(expr) => f::ceil(name, eval(expr).await?), + Function::Rand(expr) => { + let expr = match expr { + Some(v) => Some(eval(v).await?), + None => None, + }; + f::rand(name, expr) + } Function::Round(expr) => f::round(name, eval(expr).await?), Function::Floor(expr) => f::floor(name, eval(expr).await?), Function::Radians(expr) => f::radians(name, eval(expr).await?), diff --git a/core/src/executor/evaluate/stateless.rs b/core/src/executor/evaluate/stateless.rs index b035d8b82..f116353e9 100644 --- a/core/src/executor/evaluate/stateless.rs +++ b/core/src/executor/evaluate/stateless.rs @@ -228,19 +228,19 @@ fn evaluate_function<'a>(context: &Context<'_>, func: &'a Function) -> Result { let expr = eval(expr)?; let chars = eval_opt(chars.as_ref())?; - f::ltrim(name, expr, chars) + expr.ltrim(name, chars) } Function::Rtrim { expr, chars } => { let expr = eval(expr)?; let chars = eval_opt(chars.as_ref())?; - f::rtrim(name, expr, chars) + expr.rtrim(name, chars) } Function::Reverse(expr) => { let expr = eval(expr)?; @@ -258,7 +258,7 @@ fn evaluate_function<'a>(context: &Context<'_>, func: &'a Function) -> Result f::ascii(name, eval(expr)?), Function::Chr(expr) => f::chr(name, eval(expr)?), @@ -275,6 +275,7 @@ fn evaluate_function<'a>(context: &Context<'_>, func: &'a Function) -> Result f::ifnull(eval(expr)?, eval(then)?), Function::Sign(expr) => f::sign(name, eval(expr)?), Function::Ceil(expr) => f::ceil(name, eval(expr)?), + Function::Rand(expr) => f::rand(name, eval_opt(expr.as_ref())?), Function::Round(expr) => f::round(name, eval(expr)?), Function::Floor(expr) => f::floor(name, eval(expr)?), Function::Radians(expr) => f::radians(name, eval(expr)?), diff --git a/core/src/executor/execute.rs b/core/src/executor/execute.rs index 4024c215c..3cd65c535 100644 --- a/core/src/executor/execute.rs +++ b/core/src/executor/execute.rs @@ -13,7 +13,7 @@ use { TableFactor, TableWithJoins, Variable, }, data::{Key, Row, Schema, Value}, - result::MutResult, + result::Result, store::{GStore, GStoreMut}, }, futures::stream::{StreamExt, TryStreamExt}, @@ -76,9 +76,9 @@ pub enum PayloadVariable { #[cfg(feature = "transaction")] pub async fn execute_atomic( - storage: T, + storage: &mut T, statement: &Statement, -) -> MutResult { +) -> Result { if matches!( statement, Statement::StartTransaction | Statement::Rollback | Statement::Commit @@ -86,39 +86,27 @@ pub async fn execute_atomic( return execute(storage, statement).await; } - let (storage, autocommit) = storage.begin(true).await?; + let autocommit = storage.begin(true).await?; let result = execute(storage, statement).await; - match (result, autocommit) { - (Ok((storage, payload)), true) => { - let (storage, ()) = storage.commit().await?; + if !autocommit { + return result; + } - Ok((storage, payload)) - } - (Err((storage, error)), true) => { - let (storage, ()) = storage.rollback().await?; + match result { + Ok(payload) => storage.commit().await.map(|_| payload), + Err(error) => { + storage.rollback().await?; - Err((storage, error)) + Err(error) } - (result, _) => result, } } pub async fn execute( - storage: T, + storage: &mut T, statement: &Statement, -) -> MutResult { - macro_rules! try_block { - ($storage: expr, $block: block) => {{ - match (|| async { $block })().await { - Err(e) => { - return Err(($storage, e)); - } - Ok(v) => v, - } - }}; - } - +) -> Result { match statement { //- Modification //-- Tables @@ -127,19 +115,27 @@ pub async fn execute( columns, if_not_exists, source, + engine, .. - } => create_table(storage, name, columns, *if_not_exists, source) - .await - .map(|(storage, _)| (storage, Payload::Create)), + } => create_table( + storage, + name, + columns.as_ref().map(Vec::as_slice), + *if_not_exists, + source, + engine, + ) + .await + .map(|_| Payload::Create), Statement::DropTable { names, if_exists, .. } => drop_table(storage, names, *if_exists) .await - .map(|(storage, _)| (storage, Payload::DropTable)), + .map(|_| Payload::DropTable), #[cfg(feature = "alter-table")] Statement::AlterTable { name, operation } => alter_table(storage, name, operation) .await - .map(|(storage, _)| (storage, Payload::AlterTable)), + .map(|_| Payload::AlterTable), #[cfg(feature = "index")] Statement::CreateIndex { name, @@ -147,28 +143,22 @@ pub async fn execute( column, } => create_index(storage, table_name, name, column) .await - .map(|(storage, _)| (storage, Payload::CreateIndex)), + .map(|_| Payload::CreateIndex), #[cfg(feature = "index")] Statement::DropIndex { name, table_name } => storage .drop_index(table_name, name) .await - .map(|(storage, _)| (storage, Payload::DropIndex)), + .map(|_| Payload::DropIndex), //- Transaction #[cfg(feature = "transaction")] Statement::StartTransaction => storage .begin(false) .await - .map(|(storage, _)| (storage, Payload::StartTransaction)), + .map(|_| Payload::StartTransaction), #[cfg(feature = "transaction")] - Statement::Commit => storage - .commit() - .await - .map(|(storage, _)| (storage, Payload::Commit)), + Statement::Commit => storage.commit().await.map(|_| Payload::Commit), #[cfg(feature = "transaction")] - Statement::Rollback => storage - .rollback() - .await - .map(|(storage, _)| (storage, Payload::Rollback)), + Statement::Rollback => storage.rollback().await.map(|_| Payload::Rollback), //-- Rows Statement::Insert { table_name, @@ -176,62 +166,55 @@ pub async fn execute( source, } => insert(storage, table_name, columns, source) .await - .map(|(storage, num_rows)| (storage, Payload::Insert(num_rows))), + .map(Payload::Insert), Statement::Update { table_name, selection, assignments, } => { - let (table_name, rows) = try_block!(storage, { - let Schema { column_defs, .. } = storage - .fetch_schema(table_name) - .await? - .ok_or_else(|| ExecuteError::TableNotFound(table_name.to_owned()))?; + let Schema { column_defs, .. } = storage + .fetch_schema(table_name) + .await? + .ok_or_else(|| ExecuteError::TableNotFound(table_name.to_owned()))?; - let all_columns = column_defs.as_deref().map(|columns| { - columns - .iter() - .map(|col_def| col_def.name.to_owned()) - .collect() - }); - let columns_to_update = assignments + let all_columns = column_defs.as_deref().map(|columns| { + columns .iter() - .map(|assignment| assignment.id.to_owned()) - .collect(); + .map(|col_def| col_def.name.to_owned()) + .collect() + }); + let columns_to_update = assignments + .iter() + .map(|assignment| assignment.id.to_owned()) + .collect(); - let update = - Update::new(&storage, table_name, assignments, column_defs.as_deref())?; + let update = Update::new(storage, table_name, assignments, column_defs.as_deref())?; + + let rows = fetch(storage, table_name, all_columns, selection.as_ref()) + .await? + .and_then(|item| { + let update = &update; + let (key, row) = item; + + async move { + let row = update.apply(row).await?; + + Ok((key, row)) + } + }) + .try_collect::>() + .await?; + + if let Some(column_defs) = column_defs { + let column_validation = + ColumnValidation::SpecifiedColumns(Rc::from(column_defs), columns_to_update); + let rows = rows.iter().filter_map(|(_, row)| match row { + Row::Vec { values, .. } => Some(values.as_slice()), + Row::Map(_) => None, + }); - let rows = fetch(&storage, table_name, all_columns, selection.as_ref()) - .await? - .and_then(|item| { - let update = &update; - let (key, row) = item; - - async move { - let row = update.apply(row).await?; - - Ok((key, row)) - } - }) - .try_collect::>() - .await?; - - if let Some(column_defs) = column_defs { - let column_validation = ColumnValidation::SpecifiedColumns( - Rc::from(column_defs), - columns_to_update, - ); - let rows = rows.iter().filter_map(|(_, row)| match row { - Row::Vec { values, .. } => Some(values.as_slice()), - Row::Map(_) => None, - }); - - validate_unique(&storage, table_name, column_validation, rows).await?; - } - - Ok((table_name, rows)) - }); + validate_unique(storage, table_name, column_validation, rows).await?; + } let num_rows = rows.len(); let rows = rows @@ -242,69 +225,57 @@ pub async fn execute( storage .insert_data(table_name, rows) .await - .map(|(storage, _)| (storage, Payload::Update(num_rows))) + .map(|_| Payload::Update(num_rows)) } Statement::Delete { table_name, selection, } => { - let (table_name, keys) = try_block!(storage, { - let columns = fetch_columns(&storage, table_name).await?.map(Rc::from); - let keys = fetch(&storage, table_name, columns, selection.as_ref()) - .await? - .map_ok(|(key, _)| key) - .try_collect::>() - .await?; - - Ok((table_name, keys)) - }); + let columns = fetch_columns(storage, table_name).await?.map(Rc::from); + let keys = fetch(storage, table_name, columns, selection.as_ref()) + .await? + .map_ok(|(key, _)| key) + .try_collect::>() + .await?; let num_keys = keys.len(); storage .delete_data(table_name, keys) .await - .map(|(storage, _)| (storage, Payload::Delete(num_keys))) + .map(|_| Payload::Delete(num_keys)) } //- Selection Statement::Query(query) => { - let payload = try_block!(storage, { - let (labels, rows) = select_with_labels(&storage, query, None).await?; - - match labels { - Some(labels) => rows - .map(|row| row?.try_into_vec()) - .try_collect::>() - .await - .map(|rows| Payload::Select { labels, rows }), - None => rows - .map(|row| row?.try_into_map()) - .try_collect::>() - .await - .map(Payload::SelectMap), - } - }); + let (labels, rows) = select_with_labels(storage, query, None).await?; - Ok((storage, payload)) + match labels { + Some(labels) => rows + .map(|row| row?.try_into_vec()) + .try_collect::>() + .await + .map(|rows| Payload::Select { labels, rows }), + None => rows + .map(|row| row?.try_into_map()) + .try_collect::>() + .await + .map(Payload::SelectMap), + } } Statement::ShowColumns { table_name } => { - let keys = try_block!(storage, { - let Schema { column_defs, .. } = storage - .fetch_schema(table_name) - .await? - .ok_or_else(|| ExecuteError::TableNotFound(table_name.to_owned()))?; + let Schema { column_defs, .. } = storage + .fetch_schema(table_name) + .await? + .ok_or_else(|| ExecuteError::TableNotFound(table_name.to_owned()))?; - Ok(column_defs) - }); - - let output: Vec<(String, DataType)> = keys + let output: Vec<(String, DataType)> = column_defs .unwrap_or_default() .into_iter() .map(|key| (key.name, key.data_type)) .collect(); - Ok((storage, Payload::ShowColumns(output))) + Ok(Payload::ShowColumns(output)) } #[cfg(feature = "index")] Statement::ShowIndexes(table_name) => { @@ -336,22 +307,18 @@ pub async fn execute( offset: None, }; - let payload = try_block!(storage, { - let (labels, rows) = select_with_labels(&storage, &query, None).await?; - let labels = labels.unwrap_or_default(); - let rows = rows - .map(|row| row?.try_into_vec()) - .try_collect::>() - .await?; - - if rows.is_empty() { - return Err(ExecuteError::TableNotFound(table_name.to_owned()).into()); - } + let (labels, rows) = select_with_labels(storage, &query, None).await?; + let labels = labels.unwrap_or_default(); + let rows = rows + .map(|row| row?.try_into_vec()) + .try_collect::>() + .await?; - Ok(Payload::Select { labels, rows }) - }); + if rows.is_empty() { + return Err(ExecuteError::TableNotFound(table_name.to_owned()).into()); + } - Ok((storage, payload)) + Ok(Payload::Select { labels, rows }) } Statement::ShowVariable(variable) => match variable { Variable::Tables => { @@ -380,30 +347,23 @@ pub async fn execute( offset: None, }; - let rows = try_block!(storage, { - select(&storage, &query, None) - .await? - .map(|row| row?.try_into_vec()) - .try_collect::>() - .await - }); - - let table_names = rows + let table_names = select(storage, &query, None) + .await? + .map(|row| row?.try_into_vec()) + .try_collect::>>() + .await? .iter() .flat_map(|values| values.iter().map(|value| value.into())) .collect::>(); - Ok(( - storage, - Payload::ShowVariable(PayloadVariable::Tables(table_names)), - )) + Ok(Payload::ShowVariable(PayloadVariable::Tables(table_names))) } Variable::Version => { let version = var("CARGO_PKG_VERSION") .unwrap_or_else(|_| env!("CARGO_PKG_VERSION").to_owned()); let payload = Payload::ShowVariable(PayloadVariable::Version(version)); - Ok((storage, payload)) + Ok(payload) } }, } diff --git a/core/src/executor/fetch.rs b/core/src/executor/fetch.rs index 9496773dd..ab8ee8116 100644 --- a/core/src/executor/fetch.rs +++ b/core/src/executor/fetch.rs @@ -34,8 +34,8 @@ pub enum FetchError { TooManyColumnAliases(String, usize, usize), } -pub async fn fetch<'a>( - storage: &'a dyn GStore, +pub async fn fetch<'a, T: GStore>( + storage: &'a T, table_name: &'a str, columns: Option>, where_clause: Option<&'a Expr>, @@ -81,8 +81,8 @@ pub enum Rows { Dictionary(I4), } -pub async fn fetch_relation_rows<'a>( - storage: &'a dyn GStore, +pub async fn fetch_relation_rows<'a, T: GStore>( + storage: &'a T, table_factor: &'a TableFactor, filter_context: &Option>>, ) -> Result> + 'a> { @@ -341,7 +341,10 @@ pub async fn fetch_relation_rows<'a>( } } -pub async fn fetch_columns(storage: &dyn GStore, table_name: &str) -> Result>> { +pub async fn fetch_columns( + storage: &T, + table_name: &str, +) -> Result>> { let columns = storage .fetch_schema(table_name) .await? @@ -358,12 +361,34 @@ pub async fn fetch_columns(storage: &dyn GStore, table_name: &str) -> Result( + storage: &T, table_factor: &TableFactor, ) -> Result>> { match table_factor { - TableFactor::Table { name, .. } => fetch_columns(storage, name).await, + TableFactor::Table { name, alias, .. } => { + let columns = fetch_columns(storage, name).await?; + match (columns, alias) { + (columns, None) => Ok(columns), + (None, Some(_)) => Ok(None), + (Some(columns), Some(alias)) if alias.columns.len() > columns.len() => { + Err(FetchError::TooManyColumnAliases( + name.to_string(), + columns.len(), + alias.columns.len(), + ) + .into()) + } + (Some(columns), Some(alias)) => Ok(Some( + alias + .columns + .iter() + .cloned() + .chain(columns[alias.columns.len()..columns.len()].to_vec()) + .collect(), + )), + } + } TableFactor::Series { .. } => Ok(Some(vec!["N".to_owned()])), TableFactor::Dictionary { dict, .. } => Ok(Some(match dict { Dictionary::GlueObjects => vec![ @@ -387,7 +412,11 @@ pub async fn fetch_relation_columns( })), TableFactor::Derived { subquery: Query { body, .. }, - alias: TableAlias { columns, name }, + alias: + TableAlias { + columns: alias_columns, + name, + }, } => match body { SetExpr::Select(statement) => { let Select { @@ -399,11 +428,30 @@ pub async fn fetch_relation_columns( .. } = statement.as_ref(); - fetch_labels(storage, relation, joins, projection).await + let labels = fetch_labels(storage, relation, joins, projection).await?; + match labels { + None => Ok(None), + Some(labels) if alias_columns.is_empty() => Ok(Some(labels)), + Some(labels) if alias_columns.len() > labels.len() => { + Err(FetchError::TooManyColumnAliases( + name.to_string(), + labels.len(), + alias_columns.len(), + ) + .into()) + } + Some(labels) => Ok(Some( + alias_columns + .iter() + .cloned() + .chain(labels[alias_columns.len()..labels.len()].to_vec()) + .collect(), + )), + } } SetExpr::Values(Values(values_list)) => { let total_len = values_list[0].len(); - let alias_len = columns.len(); + let alias_len = alias_columns.len(); if alias_len > total_len { return Err(FetchError::TooManyColumnAliases( name.into(), @@ -415,7 +463,11 @@ pub async fn fetch_relation_columns( let labels = (alias_len + 1..=total_len) .into_iter() .map(|i| format!("column{}", i)); - let labels = columns.iter().cloned().chain(labels).collect::>(); + let labels = alias_columns + .iter() + .cloned() + .chain(labels) + .collect::>(); Ok(Some(labels)) } @@ -423,9 +475,9 @@ pub async fn fetch_relation_columns( } } -async fn fetch_join_columns<'a>( +async fn fetch_join_columns<'a, T: GStore>( + storage: &T, joins: &'a [Join], - storage: &dyn GStore, ) -> Result)>>> { let columns = stream::iter(joins.iter()) .map(Ok::<_, Error>) @@ -443,15 +495,15 @@ async fn fetch_join_columns<'a>( Ok((columns.len() == joins.len()).then_some(columns)) } -pub async fn fetch_labels( - storage: &dyn GStore, +pub async fn fetch_labels( + storage: &T, relation: &TableFactor, joins: &[Join], projection: &[SelectItem], ) -> Result>> { let table_alias = get_alias(relation); let columns = fetch_relation_columns(storage, relation).await?; - let join_columns = fetch_join_columns(joins, storage).await?; + let join_columns = fetch_join_columns(storage, joins).await?; if (columns.is_none() || join_columns.is_none()) && projection.iter().any(|item| { diff --git a/core/src/executor/filter.rs b/core/src/executor/filter.rs index bd153e621..0a7faffe9 100644 --- a/core/src/executor/filter.rs +++ b/core/src/executor/filter.rs @@ -10,16 +10,16 @@ use { std::rc::Rc, }; -pub struct Filter<'a> { - storage: &'a dyn GStore, +pub struct Filter<'a, T: GStore> { + storage: &'a T, where_clause: Option<&'a Expr>, context: Option>>, aggregated: Option>>, } -impl<'a> Filter<'a> { +impl<'a, T: GStore> Filter<'a, T> { pub fn new( - storage: &'a dyn GStore, + storage: &'a T, where_clause: Option<&'a Expr>, context: Option>>, aggregated: Option>>, @@ -51,8 +51,8 @@ impl<'a> Filter<'a> { } } -pub async fn check_expr<'a>( - storage: &'a dyn GStore, +pub async fn check_expr<'a, T: GStore>( + storage: &'a T, context: Option>>, aggregated: Option>>, expr: &'a Expr, diff --git a/core/src/executor/insert.rs b/core/src/executor/insert.rs index 5b11ab9a5..71b35b458 100644 --- a/core/src/executor/insert.rs +++ b/core/src/executor/insert.rs @@ -7,7 +7,7 @@ use { ast::{ColumnDef, ColumnUniqueOption, Expr, Query, SetExpr, Values}, data::{Key, Row, Schema, Value}, executor::{evaluate::evaluate_stateless, limit::Limit}, - result::{MutResult, Result, TrySelf}, + result::Result, store::{DataRow, GStore, GStoreMut}, }, futures::stream::{self, StreamExt, TryStreamExt}, @@ -46,47 +46,44 @@ enum RowsData { } pub async fn insert( - storage: T, + storage: &mut T, table_name: &str, columns: &[String], source: &Query, -) -> MutResult { - let rows = (|| async { - let Schema { column_defs, .. } = storage - .fetch_schema(table_name) - .await? - .ok_or_else(|| InsertError::TableNotFound(table_name.to_owned()))?; - - match column_defs { - Some(column_defs) => { - fetch_vec_rows(&storage, table_name, column_defs, columns, source).await - } - None => fetch_map_rows(&storage, source).await.map(RowsData::Append), +) -> Result { + let Schema { column_defs, .. } = storage + .fetch_schema(table_name) + .await? + .ok_or_else(|| InsertError::TableNotFound(table_name.to_owned()))?; + + let rows = match column_defs { + Some(column_defs) => { + fetch_vec_rows(storage, table_name, column_defs, columns, source).await } - })() - .await; + None => fetch_map_rows(storage, source).await.map(RowsData::Append), + }?; - match rows.try_self(storage)? { - (storage, RowsData::Append(rows)) => { + match rows { + RowsData::Append(rows) => { let num_rows = rows.len(); storage .append_data(table_name, rows) .await - .map(|(storage, _)| (storage, num_rows)) + .map(|_| num_rows) } - (storage, RowsData::Insert(rows)) => { + RowsData::Insert(rows) => { let num_rows = rows.len(); storage .insert_data(table_name, rows) .await - .map(|(storage, _)| (storage, num_rows)) + .map(|_| num_rows) } } } -async fn fetch_vec_rows( +async fn fetch_vec_rows( storage: &T, table_name: &str, column_defs: Vec, @@ -177,10 +174,7 @@ async fn fetch_vec_rows( } } -async fn fetch_map_rows( - storage: &T, - source: &Query, -) -> Result> { +async fn fetch_map_rows(storage: &T, source: &Query) -> Result> { #[derive(futures_enum::Stream)] enum Rows { Values(I1), diff --git a/core/src/executor/join.rs b/core/src/executor/join.rs index 510a8b4a6..242dc1e0f 100644 --- a/core/src/executor/join.rs +++ b/core/src/executor/join.rs @@ -19,8 +19,8 @@ use { utils::OrStream, }; -pub struct Join<'a> { - storage: &'a dyn GStore, +pub struct Join<'a, T: GStore> { + storage: &'a T, join_clauses: &'a [AstJoin], filter_context: Option>>, } @@ -29,9 +29,9 @@ type JoinItem<'a> = Rc>; type Joined<'a> = Pin, Error = Error, Item = Result>> + 'a>>; -impl<'a> Join<'a> { +impl<'a, T: GStore> Join<'a, T> { pub fn new( - storage: &'a dyn GStore, + storage: &'a T, join_clauses: &'a [AstJoin], filter_context: Option>>, ) -> Self { @@ -59,8 +59,8 @@ impl<'a> Join<'a> { } } -async fn join<'a>( - storage: &'a dyn GStore, +async fn join<'a, T: GStore>( + storage: &'a T, filter_context: Option>>, ast_join: &'a AstJoin, left_rows: impl TryStream, Error = Error, Item = Result>> + 'a, @@ -214,8 +214,8 @@ enum JoinExecutor<'a> { } impl<'a> JoinExecutor<'a> { - async fn new( - storage: &'a dyn GStore, + async fn new( + storage: &'a T, relation: &TableFactor, filter_context: Option>>, ast_join_executor: &'a AstJoinExecutor, @@ -273,8 +273,8 @@ impl<'a> JoinExecutor<'a> { } } -async fn check_where_clause<'a, 'b>( - storage: &'a dyn GStore, +async fn check_where_clause<'a, 'b, T: GStore>( + storage: &'a T, table_alias: &'a str, filter_context: Option>>, project_context: Option>>, diff --git a/core/src/executor/select/mod.rs b/core/src/executor/select/mod.rs index 3feeab57f..9f1328058 100644 --- a/core/src/executor/select/mod.rs +++ b/core/src/executor/select/mod.rs @@ -97,7 +97,7 @@ fn sort_stateless(rows: Vec>, order_by: &[OrderByExpr]) -> Result>>() .map(Vector::from)? - .sort_by(|(values_a, _), (values_b, _)| Sort::sort_by(values_a, values_b)) + .sort_by(|(values_a, _), (values_b, _)| super::sort::sort_by(values_a, values_b)) .into_iter() .map(|(_, row)| row) .collect::>(); @@ -106,8 +106,8 @@ fn sort_stateless(rows: Vec>, order_by: &[OrderByExpr]) -> Result( - storage: &'a dyn GStore, +pub async fn select_with_labels<'a, T: GStore>( + storage: &'a T, query: &'a Query, filter_context: Option>>, ) -> Result<( @@ -206,8 +206,8 @@ pub async fn select_with_labels<'a>( Ok((labels, rows)) } -pub async fn select<'a>( - storage: &'a dyn GStore, +pub async fn select<'a, T: GStore>( + storage: &'a T, query: &'a Query, filter_context: Option>>, ) -> Result> + 'a> { diff --git a/core/src/executor/select/project.rs b/core/src/executor/select/project.rs index a57567d23..b91f2c95e 100644 --- a/core/src/executor/select/project.rs +++ b/core/src/executor/select/project.rs @@ -11,15 +11,15 @@ use { std::rc::Rc, }; -pub struct Project<'a> { - storage: &'a dyn GStore, +pub struct Project<'a, T: GStore> { + storage: &'a T, context: Option>>, fields: &'a [SelectItem], } -impl<'a> Project<'a> { +impl<'a, T: GStore> Project<'a, T> { pub fn new( - storage: &'a dyn GStore, + storage: &'a T, context: Option>>, fields: &'a [SelectItem], ) -> Self { diff --git a/core/src/executor/sort.rs b/core/src/executor/sort.rs index 7b9d7331e..c940e6393 100644 --- a/core/src/executor/sort.rs +++ b/core/src/executor/sort.rs @@ -23,15 +23,15 @@ pub enum SortError { Unreachable, } -pub struct Sort<'a> { - storage: &'a dyn GStore, +pub struct Sort<'a, T: GStore> { + storage: &'a T, context: Option>>, order_by: &'a [OrderByExpr], } -impl<'a> Sort<'a> { +impl<'a, T: GStore> Sort<'a, T> { pub fn new( - storage: &'a dyn GStore, + storage: &'a T, context: Option>>, order_by: &'a [OrderByExpr], ) -> Self { @@ -155,45 +155,42 @@ impl<'a> Sort<'a> { .try_collect::)>, Row)>>() .await .map(Vector::from)? - .sort_by(|(values_a, ..), (values_b, ..)| Self::sort_by(values_a, values_b)) + .sort_by(|(values_a, ..), (values_b, ..)| sort_by(values_a, values_b)) .into_iter() .map(|(.., row)| Ok(row)); Ok(Rows::OrderBy(stream::iter(rows))) } +} - pub fn sort_by( - values_a: &[(Value, Option)], - values_b: &[(Value, Option)], - ) -> Ordering { - let pairs = values_a - .iter() - .map(|(a, _)| a) - .zip(values_b.iter()) - .map(|(a, (b, asc))| (a, b, asc.unwrap_or(true))); - - for (value_a, value_b, asc) in pairs { - let apply_asc = |ord: Ordering| if asc { ord } else { ord.reverse() }; - - match (value_a, value_b) { - (Value::Null, Value::Null) => {} - (Value::Null, _) => { - return apply_asc(Ordering::Greater); - } - (_, Value::Null) => { - return apply_asc(Ordering::Less); - } - _ => {} - }; +pub fn sort_by(values_a: &[(Value, Option)], values_b: &[(Value, Option)]) -> Ordering { + let pairs = values_a + .iter() + .map(|(a, _)| a) + .zip(values_b.iter()) + .map(|(a, (b, asc))| (a, b, asc.unwrap_or(true))); - match value_a.partial_cmp(value_b) { - Some(ord) if ord != Ordering::Equal => { - return apply_asc(ord); - } - _ => {} + for (value_a, value_b, asc) in pairs { + let apply_asc = |ord: Ordering| if asc { ord } else { ord.reverse() }; + + match (value_a, value_b) { + (Value::Null, Value::Null) => {} + (Value::Null, _) => { + return apply_asc(Ordering::Greater); } - } + (_, Value::Null) => { + return apply_asc(Ordering::Less); + } + _ => {} + }; - Ordering::Equal + match value_a.partial_cmp(value_b) { + Some(ord) if ord != Ordering::Equal => { + return apply_asc(ord); + } + _ => {} + } } + + Ordering::Equal } diff --git a/core/src/executor/update.rs b/core/src/executor/update.rs index e7013c179..fa0872fba 100644 --- a/core/src/executor/update.rs +++ b/core/src/executor/update.rs @@ -28,16 +28,16 @@ pub enum UpdateError { ConflictOnSchema, } -pub struct Update<'a> { - storage: &'a dyn GStore, +pub struct Update<'a, T: GStore> { + storage: &'a T, table_name: &'a str, fields: &'a [Assignment], column_defs: Option<&'a [ColumnDef]>, } -impl<'a> Update<'a> { +impl<'a, T: GStore> Update<'a, T> { pub fn new( - storage: &'a dyn GStore, + storage: &'a T, table_name: &'a str, fields: &'a [Assignment], column_defs: Option<&'a [ColumnDef]>, @@ -95,6 +95,10 @@ impl<'a> Update<'a> { v.validate_type(data_type)?; v } + Evaluated::StrSlice { + source: s, + range: r, + } => Value::Str(s[r].to_owned()), }; value.validate_null(*nullable)?; diff --git a/core/src/executor/validate.rs b/core/src/executor/validate.rs index 5baa48013..b57c01c6a 100644 --- a/core/src/executor/validate.rs +++ b/core/src/executor/validate.rs @@ -81,8 +81,8 @@ impl UniqueConstraint { } } -pub async fn validate_unique( - storage: &dyn Store, +pub async fn validate_unique( + storage: &T, table_name: &str, column_validation: ColumnValidation, row_iter: impl Iterator + Clone, diff --git a/core/src/glue.rs b/core/src/glue.rs index 771c577e9..a695ac2bf 100644 --- a/core/src/glue.rs +++ b/core/src/glue.rs @@ -16,19 +16,17 @@ use { }; pub struct Glue { - pub storage: Option, + pub storage: T, } impl Glue { pub fn new(storage: T) -> Self { - Self { - storage: Some(storage), - } + Self { storage } } - pub async fn plan>(&self, sql: Sql) -> Result> { + pub async fn plan>(&mut self, sql: Sql) -> Result> { let parsed = parse(sql)?; - let storage = self.storage.as_ref().unwrap(); + let storage = &self.storage; stream::iter(parsed) .map(|p| translate(&p)) .then(|statement| async move { plan(storage, statement?).await }) @@ -46,20 +44,7 @@ impl Glue { } pub async fn execute_stmt_async(&mut self, statement: &Statement) -> Result { - let storage = self.storage.take().unwrap(); - - match execute(storage, statement).await { - Ok((storage, payload)) => { - self.storage = Some(storage); - - Ok(payload) - } - Err((storage, error)) => { - self.storage = Some(storage); - - Err(error) - } - } + execute(&mut self.storage, statement).await } pub async fn execute_async>(&mut self, sql: Sql) -> Result> { diff --git a/core/src/lib.rs b/core/src/lib.rs index 02993394a..3e8a7c564 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,6 +23,7 @@ pub mod prelude { glue::Glue, parse_sql::parse, plan::plan, + result::Error, translate::translate, }; } diff --git a/core/src/plan/evaluable.rs b/core/src/plan/evaluable.rs index 0ad2f2ae9..9fa1c342f 100644 --- a/core/src/plan/evaluable.rs +++ b/core/src/plan/evaluable.rs @@ -140,15 +140,15 @@ fn check_table_factor(context: Option>>, table_factor: &TableFact let alias = match table_factor { TableFactor::Table { name, alias, .. } => alias .as_ref() - .map(|TableAlias { name, .. }| name.clone()) - .unwrap_or_else(|| name.clone()), + .map(|TableAlias { name, .. }| name) + .unwrap_or_else(|| name), TableFactor::Derived { alias, .. } | TableFactor::Series { alias, .. } - | TableFactor::Dictionary { alias, .. } => alias.name.to_owned(), + | TableFactor::Dictionary { alias, .. } => &alias.name, }; context - .map(|context| context.contains_alias(&alias)) + .map(|context| context.contains_alias(alias)) .unwrap_or(false) } diff --git a/core/src/plan/expr/function.rs b/core/src/plan/expr/function.rs index e2c5974ed..142fc0f6b 100644 --- a/core/src/plan/expr/function.rs +++ b/core/src/plan/expr/function.rs @@ -16,7 +16,9 @@ impl Function { } match self { - Self::Now() | Function::Pi() | Function::GenerateUuid() => Exprs::Empty(empty()), + Self::Now() | Function::Pi() | Function::GenerateUuid() | Self::Rand(None) => { + Exprs::Empty(empty()) + } Self::Lower(expr) | Self::Upper(expr) | Self::Sin(expr) @@ -28,6 +30,7 @@ impl Function { | Self::Radians(expr) | Self::Degrees(expr) | Self::Ceil(expr) + | Self::Rand(Some(expr)) | Self::Round(expr) | Self::Floor(expr) | Self::Exp(expr) @@ -180,6 +183,7 @@ mod tests { test("NOW()", &[]); test("PI()", &[]); test("GENERATE_UUID()", &[]); + test("RAND()", &[]); // Single test("LOWER(id)", &["id"]); @@ -193,6 +197,7 @@ mod tests { test("RADIANS(180)", &["180"]); test("DEGREES(3.14)", &["3.14"]); test("CEIL(1.23)", &["1.23"]); + test("Rand(1.23)", &["1.23"]); test("ROUND(1.23)", &["1.23"]); test("FLOOR(1.23)", &["1.23"]); test("EXP(1.23)", &["1.23"]); diff --git a/core/src/plan/mock.rs b/core/src/plan/mock.rs index 9585f6509..e020b92dc 100644 --- a/core/src/plan/mock.rs +++ b/core/src/plan/mock.rs @@ -11,31 +11,25 @@ use { data::{Key, Schema}, executor::execute, parse_sql::parse, - result::{Error, MutResult, Result}, + result::{Error, Result}, store::{DataRow, RowIter, Store, StoreMut}, translate::translate, }, async_trait::async_trait, - futures::{ - executor::block_on, - stream::{self, StreamExt}, - }, + futures::executor::block_on, std::collections::HashMap, }; pub fn run(sql: &str) -> MockStorage { - let storage = - stream::iter(parse(sql).unwrap()).fold(MockStorage::default(), |storage, parsed| { - let statement = translate(&parsed).unwrap(); + let mut storage = MockStorage::default(); - async move { - let (storage, _) = execute(storage, &statement).await.unwrap(); + for parsed in parse(sql).unwrap() { + let statement = translate(&parsed).unwrap(); - storage - } - }); + block_on(execute(&mut storage, &statement)).unwrap(); + } - block_on(storage) + storage } #[derive(Default, Debug)] @@ -79,42 +73,36 @@ impl Store for MockStorage { #[async_trait(?Send)] impl StoreMut for MockStorage { - async fn insert_schema(self, schema: &Schema) -> MutResult { - let mut storage = self; - + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { let table_name = schema.table_name.clone(); let schema = schema.clone(); - storage.schema_map.insert(table_name, schema); - Ok((storage, ())) + self.schema_map.insert(table_name, schema); + Ok(()) } - async fn delete_schema(self, _table_name: &str) -> MutResult { + async fn delete_schema(&mut self, _table_name: &str) -> Result<()> { let msg = "[MockStorage] delete_schema is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } - async fn append_data(self, _table_name: &str, _rows: Vec) -> MutResult { + async fn append_data(&mut self, _table_name: &str, _rows: Vec) -> Result<()> { let msg = "[MockStorage] append_data is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } - async fn insert_data( - self, - _table_name: &str, - _rows: Vec<(Key, DataRow)>, - ) -> MutResult { + async fn insert_data(&mut self, _table_name: &str, _rows: Vec<(Key, DataRow)>) -> Result<()> { let msg = "[MockStorage] insert_data is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } - async fn delete_data(self, _table_name: &str, _keys: Vec) -> MutResult { + async fn delete_data(&mut self, _table_name: &str, _keys: Vec) -> Result<()> { let msg = "[MockStorage] delete_data is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } } @@ -147,40 +135,28 @@ mod tests { super::MockStorage, crate::{ data::Key, - result::MutResult, store::{Store, StoreMut}, }, futures::executor::block_on, - std::future::Future, }; - fn test(result: F) -> MockStorage - where - F: Future>, - { - match block_on(result) { - Ok(_) => unreachable!("this test must fail"), - Err((storage, _)) => storage, - } - } - #[test] fn empty() { - let storage = MockStorage::default(); + let mut storage = MockStorage::default(); assert!(block_on(storage.scan_data("Foo")).is_err()); assert!(block_on(storage.fetch_data("Foo", &Key::None)).is_err()); assert!(block_on(storage.fetch_schema("__Err__")).is_err()); - let storage = test(storage.delete_schema("Foo")); - let storage = test(storage.append_data("Foo", Vec::new())); - let storage = test(storage.insert_data("Foo", Vec::new())); - let storage = test(storage.delete_data("Foo", Vec::new())); + assert!(block_on(storage.delete_schema("Foo")).is_err()); + assert!(block_on(storage.append_data("Foo", Vec::new())).is_err()); + assert!(block_on(storage.insert_data("Foo", Vec::new())).is_err()); + assert!(block_on(storage.delete_data("Foo", Vec::new())).is_err()); #[cfg(feature = "alter-table")] - let storage = { - let storage = test(storage.rename_schema("Foo", "Bar")); - let storage = test(storage.rename_column("Foo", "col_old", "col_new")); - let storage = test(storage.add_column( + { + assert!(block_on(storage.rename_schema("Foo", "Bar")).is_err()); + assert!(block_on(storage.rename_column("Foo", "col_old", "col_new")).is_err()); + assert!(block_on(storage.add_column( "Foo", &ColumnDef { name: "new_col".to_owned(), @@ -189,16 +165,15 @@ mod tests { default: None, unique: None, }, - )); - let storage = test(storage.drop_column("Foo", "col", false)); - - storage + )) + .is_err()); + assert!(block_on(storage.drop_column("Foo", "col", false)).is_err()); }; #[cfg(feature = "index")] - let storage = { + { assert!(block_on(storage.scan_indexed_data("Foo", "idx_col", None, None)).is_err()); - let storage = test(storage.create_index( + assert!(block_on(storage.create_index( "Foo", "idx_col", &OrderByExpr { @@ -208,19 +183,16 @@ mod tests { }, asc: None, }, - )); - let storage = test(storage.drop_index("Foo", "idx_col")); - - storage + )) + .is_err()); + assert!(block_on(storage.drop_index("Foo", "idx_col")).is_err()); }; #[cfg(feature = "transaction")] - let storage = { - let storage = test(storage.begin(false)); - let storage = test(storage.rollback()); - let storage = test(storage.commit()); - - storage + { + assert!(block_on(storage.begin(false)).is_err()); + assert!(block_on(storage.rollback()).is_ok()); + assert!(block_on(storage.commit()).is_ok()); }; assert!(matches!(block_on(storage.fetch_schema("Foo")), Ok(None))); diff --git a/core/src/plan/mod.rs b/core/src/plan/mod.rs index 34bc2144d..eaa64aaaa 100644 --- a/core/src/plan/mod.rs +++ b/core/src/plan/mod.rs @@ -19,11 +19,9 @@ pub use { primary_key::plan as plan_primary_key, schema::fetch_schema_map, }; -pub async fn plan(storage: &dyn Store, statement: Statement) -> Result { +pub async fn plan(storage: &T, statement: Statement) -> Result { let schema_map = fetch_schema_map(storage, &statement).await?; - - let statement = validate(&schema_map, statement)?; - + validate(&schema_map, &statement)?; let statement = plan_primary_key(&schema_map, statement); let statement = plan_index(&schema_map, statement)?; let statement = plan_join(&schema_map, statement); diff --git a/core/src/plan/schema.rs b/core/src/plan/schema.rs index 89ca68cd3..f3f52e800 100644 --- a/core/src/plan/schema.rs +++ b/core/src/plan/schema.rs @@ -10,38 +10,40 @@ use { store::Store, }, async_recursion::async_recursion, - futures::{ - future, - stream::{self, StreamExt, TryStreamExt}, - }, + futures::stream::{self, StreamExt, TryStreamExt}, std::collections::HashMap, }; -pub async fn fetch_schema_map( - storage: &dyn Store, +pub async fn fetch_schema_map( + storage: &T, statement: &Statement, ) -> Result> { match statement { - Statement::Query(query) => scan_query(storage, query).await.map(|schema_list| { - schema_list - .into_iter() - .map(|schema| (schema.table_name.clone(), schema)) - .collect::>() - }), + Statement::Query(query) => scan_query(storage, query).await, Statement::Insert { table_name, source, .. } => { let table_schema = storage .fetch_schema(table_name) .await? - .map(|schema| vec![schema]) - .unwrap_or_else(Vec::new); + .map(|schema| HashMap::from([(table_name.to_owned(), schema)])) + .unwrap_or_else(HashMap::new); let source_schema_list = scan_query(storage, source).await?; - let schema_list = [table_schema, source_schema_list] - .into_iter() - .flatten() - .map(|schema| (schema.table_name.clone(), schema)) - .collect(); + let schema_list = table_schema.into_iter().chain(source_schema_list).collect(); + + Ok(schema_list) + } + Statement::CreateTable { name, source, .. } => { + let table_schema = storage + .fetch_schema(name) + .await? + .map(|schema| HashMap::from([(name.to_owned(), schema)])) + .unwrap_or_else(HashMap::new); + let source_schema_list = match source { + Some(source) => scan_query(storage, source).await?, + None => HashMap::new(), + }; + let schema_list = table_schema.into_iter().chain(source_schema_list).collect(); Ok(schema_list) } @@ -61,7 +63,7 @@ pub async fn fetch_schema_map( } } -async fn scan_query(storage: &dyn Store, query: &Query) -> Result> { +async fn scan_query(storage: &T, query: &Query) -> Result> { let Query { body, limit, @@ -71,7 +73,7 @@ async fn scan_query(storage: &dyn Store, query: &Query) -> Result> { let schema_list = match body { SetExpr::Select(select) => scan_select(storage, select).await?, - SetExpr::Values(_) => Vec::new(), + SetExpr::Values(_) => HashMap::new(), }; let schema_list = match (limit, offset) { @@ -90,7 +92,7 @@ async fn scan_query(storage: &dyn Store, query: &Query) -> Result> { Ok(schema_list) } -async fn scan_select(storage: &dyn Store, select: &Select) -> Result> { +async fn scan_select(storage: &T, select: &Select) -> Result> { let Select { projection, from, @@ -100,13 +102,13 @@ async fn scan_select(storage: &dyn Store, select: &Select) -> Result } = select; let projection = stream::iter(projection) - .then(|select_item| match select_item { - SelectItem::Expr { expr, .. } => scan_expr(storage, expr), - SelectItem::QualifiedWildcard(_) | SelectItem::Wildcard => { - Box::pin(future::ok(Vec::new())) + .then(|select_item| async move { + match select_item { + SelectItem::Expr { expr, .. } => scan_expr(storage, expr).await, + SelectItem::QualifiedWildcard(_) | SelectItem::Wildcard => Ok(HashMap::new()), } }) - .try_collect::>>() + .try_collect::>>() .await? .into_iter() .flatten(); @@ -117,7 +119,7 @@ async fn scan_select(storage: &dyn Store, select: &Select) -> Result Ok(stream::iter(exprs) .then(|expr| scan_expr(storage, expr)) - .try_collect::>>() + .try_collect::>>() .await? .into_iter() .flatten() @@ -126,16 +128,16 @@ async fn scan_select(storage: &dyn Store, select: &Select) -> Result .collect()) } -async fn scan_table_with_joins( - storage: &dyn Store, +async fn scan_table_with_joins( + storage: &T, table_with_joins: &TableWithJoins, -) -> Result> { +) -> Result> { let TableWithJoins { relation, joins } = table_with_joins; let schema_list = scan_table_factor(storage, relation).await?; Ok(stream::iter(joins) .then(|join| scan_join(storage, join)) - .try_collect::>>() + .try_collect::>>() .await? .into_iter() .flatten() @@ -143,7 +145,7 @@ async fn scan_table_with_joins( .collect()) } -async fn scan_join(storage: &dyn Store, join: &Join) -> Result> { +async fn scan_join(storage: &T, join: &Join) -> Result> { let Join { relation, join_operator, @@ -166,24 +168,29 @@ async fn scan_join(storage: &dyn Store, join: &Join) -> Result> { } #[async_recursion(?Send)] -async fn scan_table_factor(storage: &dyn Store, table_factor: &TableFactor) -> Result> { +async fn scan_table_factor( + storage: &T, + table_factor: &TableFactor, +) -> Result> { match table_factor { TableFactor::Table { name, .. } => { let schema = storage.fetch_schema(name).await?; - let schema_list = schema.map(|schema| vec![schema]).unwrap_or_else(Vec::new); + let schema_list: HashMap = schema.map_or_else(HashMap::new, |schema| { + HashMap::from([(name.to_owned(), schema)]) + }); Ok(schema_list) } TableFactor::Derived { subquery, .. } => scan_query(storage, subquery).await, - TableFactor::Series { .. } | TableFactor::Dictionary { .. } => Ok(vec![]), + TableFactor::Series { .. } | TableFactor::Dictionary { .. } => Ok(HashMap::new()), } } #[async_recursion(?Send)] -async fn scan_expr(storage: &dyn Store, expr: &Expr) -> Result> { +async fn scan_expr(storage: &T, expr: &Expr) -> Result> { let schema_list = match expr.into() { PlanExpr::None | PlanExpr::Identifier(_) | PlanExpr::CompoundIdentifier { .. } => { - Vec::new() + HashMap::new() } PlanExpr::Expr(expr) => scan_expr(storage, expr).await?, PlanExpr::TwoExprs(expr, expr2) => scan_expr(storage, expr) @@ -199,7 +206,7 @@ async fn scan_expr(storage: &dyn Store, expr: &Expr) -> Result> { .collect(), PlanExpr::MultiExprs(exprs) => stream::iter(exprs) .then(|expr| scan_expr(storage, expr)) - .try_collect::>>() + .try_collect::>>() .await? .into_iter() .flatten() diff --git a/core/src/plan/validate.rs b/core/src/plan/validate.rs index a88a3f09f..53451383b 100644 --- a/core/src/plan/validate.rs +++ b/core/src/plan/validate.rs @@ -1,50 +1,186 @@ use { super::PlanError, crate::{ - ast::{Expr, SelectItem, SetExpr, Statement}, + ast::{Expr, Join, Query, SelectItem, SetExpr, Statement, TableFactor, TableWithJoins}, data::Schema, result::Result, }, - std::collections::HashMap, + std::{collections::HashMap, rc::Rc}, }; +type SchemaMap = HashMap; /// Validate user select column should not be ambiguous -pub fn validate(schema_map: &HashMap, statement: Statement) -> Result { - if let Statement::Query(query) = &statement { - if let SetExpr::Select(select) = &query.body { - if !select.from.joins.is_empty() { - select - .projection - .iter() - .map(|select_item| { - if let SelectItem::Expr { - expr: Expr::Identifier(ident), - .. - } = select_item - { - let tables_with_given_col = - schema_map.iter().filter_map(|(_, schema)| { - match schema.column_defs.as_ref() { - Some(column_defs) => { - column_defs.iter().find(|col| &col.name == ident) - } - None => None, - } - }); - - if tables_with_given_col.count() > 1 { - return Err( - PlanError::ColumnReferenceAmbiguous(ident.to_owned()).into() - ); - } - } - - Ok(()) - }) - .collect::>>()?; +pub fn validate(schema_map: &SchemaMap, statement: &Statement) -> Result<()> { + let query = match statement { + Statement::Query(query) => Some(query), + Statement::Insert { source, .. } => Some(source), + Statement::CreateTable { source, .. } => source.as_deref(), + _ => None, + }; + + if let Some(query) = query { + if let Query { + body: SetExpr::Select(select), + .. + } = query + { + for select_item in &select.projection { + if let SelectItem::Expr { + expr: Expr::Identifier(ident), + .. + } = select_item + { + if let Some(context) = contextualize_query(schema_map, query) { + context.validate_duplicated(ident)?; + } + } } } } - Ok(statement) + Ok(()) +} + +enum Context<'a> { + Data { + labels: Option>, + next: Option>>, + }, + Bridge { + left: Rc>, + right: Rc>, + }, +} + +impl<'a> Context<'a> { + fn new(labels: Option>, next: Option>>) -> Self { + Self::Data { labels, next } + } + + fn concat(left: Option>>, right: Option>>) -> Option> { + match (left, right) { + (Some(left), Some(right)) => Some(Rc::new(Self::Bridge { left, right })), + (context @ Some(_), None) | (None, context @ Some(_)) => context, + (None, None) => None, + } + } + + fn validate_duplicated(&self, column_name: &str) -> Result<()> { + fn validate(context: &Context, column_name: &str) -> Result { + let (left, right) = match context { + Context::Data { labels, next, .. } => { + let current = labels + .as_ref() + .map(|labels| labels.iter().any(|label| *label == column_name)) + .unwrap_or(false); + + let next = next + .as_ref() + .map(|next| validate(next, column_name)) + .unwrap_or(Ok(false))?; + + (current, next) + } + Context::Bridge { left, right } => { + let left = validate(left, column_name)?; + let right = validate(right, column_name)?; + + (left, right) + } + }; + + if left && right { + Err(PlanError::ColumnReferenceAmbiguous(column_name.to_owned()).into()) + } else { + Ok(left || right) + } + } + + validate(self, column_name).map(|_| ()) + } +} + +fn get_lables(schema: &Schema) -> Option> { + schema.column_defs.as_ref().map(|column_defs| { + column_defs + .iter() + .map(|column_def| column_def.name.as_str()) + .collect::>() + }) +} + +fn contextualize_query<'a>(schema_map: &'a SchemaMap, query: &'a Query) -> Option>> { + let Query { body, .. } = query; + match body { + SetExpr::Select(select) => { + let TableWithJoins { relation, joins } = &select.from; + let by_table = contextualize_table_factor(schema_map, relation); + let by_joins = joins + .iter() + .map(|Join { relation, .. }| contextualize_table_factor(schema_map, relation)) + .fold(None, Context::concat); + + Context::concat(by_table, by_joins) + } + SetExpr::Values(_) => None, + } +} + +fn contextualize_table_factor<'a>( + schema_map: &'a SchemaMap, + table_factor: &'a TableFactor, +) -> Option>> { + match table_factor { + TableFactor::Table { name, .. } => { + let schema = schema_map.get(name); + schema.map(|schema| Rc::from(Context::new(get_lables(schema), None))) + } + TableFactor::Derived { subquery, .. } => contextualize_query(schema_map, subquery), + TableFactor::Series { .. } | TableFactor::Dictionary { .. } => None, + } + .map(Rc::from) +} + +#[cfg(test)] +mod tests { + use { + crate::{ + plan::{fetch_schema_map, mock::run, validate}, + prelude::{parse, translate}, + }, + futures::executor::block_on, + }; + + #[test] + fn validate_test() { + let storage = run(" + CREATE TABLE Users ( + id INTEGER, + name TEXT + ); + "); + + let cases = [ + ("SELECT * FROM (SELECT * FROM Users) AS Sub", true), + ("SELECT * FROM SERIES(3)", true), + ("SELECT id FROM Users A JOIN Users B on A.id = B.id", false), + ( + "INSERT INTO Users SELECT id FROM Users A JOIN Users B on A.id = B.id", + false, + ), + ( + "CREATE TABLE Ids AS SELECT id FROM Users A JOIN Users B on A.id = B.id", + false, + ), + ]; + + for (sql, expected) in cases { + let parsed = parse(sql).expect(sql).into_iter().next().unwrap(); + let statement = translate(&parsed).unwrap(); + let schema_map = block_on(fetch_schema_map(&storage, &statement)).unwrap(); + let actual = validate(&schema_map, &statement).is_ok(); + + assert_eq!(actual, expected) + } + } } diff --git a/core/src/result.rs b/core/src/result.rs index dbd17feed..1dc356144 100644 --- a/core/src/result.rs +++ b/core/src/result.rs @@ -9,7 +9,6 @@ use { SelectError, SortError, UpdateError, ValidateError, }, plan::PlanError, - store::{GStore, GStoreMut}, translate::TranslateError, }, serde::Serialize, @@ -88,7 +87,6 @@ pub enum Error { } pub type Result = std::result::Result; -pub type MutResult = std::result::Result<(T, U), (T, Error)>; impl PartialEq for Error { fn eq(&self, other: &Error) -> bool { @@ -126,22 +124,6 @@ impl PartialEq for Error { } } -pub trait TrySelf -where - Self: Sized, -{ - fn try_self(self, storage: T) -> MutResult; -} - -impl TrySelf for Result { - fn try_self(self, storage: T) -> MutResult { - match self { - Ok(v) => Ok((storage, v)), - Err(e) => Err((storage, e)), - } - } -} - mod stringify { use {serde::Serializer, std::fmt::Display}; diff --git a/core/src/store/alter_table.rs b/core/src/store/alter_table.rs index 3ac776e9e..2a9c36053 100644 --- a/core/src/store/alter_table.rs +++ b/core/src/store/alter_table.rs @@ -1,7 +1,7 @@ use { crate::{ ast::ColumnDef, - result::{Error, MutResult}, + result::{Error, Result}, }, async_trait::async_trait, serde::Serialize, @@ -31,41 +31,38 @@ pub enum AlterTableError { } #[async_trait(?Send)] -pub trait AlterTable -where - Self: Sized, -{ - async fn rename_schema(self, _table_name: &str, _new_table_name: &str) -> MutResult { +pub trait AlterTable { + async fn rename_schema(&mut self, _table_name: &str, _new_table_name: &str) -> Result<()> { let msg = "[Storage] AlterTable::rename_schema is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } async fn rename_column( - self, + &mut self, _table_name: &str, _old_column_name: &str, _new_column_name: &str, - ) -> MutResult { + ) -> Result<()> { let msg = "[Storage] AlterTable::rename_column is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } - async fn add_column(self, _table_name: &str, _column_def: &ColumnDef) -> MutResult { + async fn add_column(&mut self, _table_name: &str, _column_def: &ColumnDef) -> Result<()> { let msg = "[Storage] AlterTable::add_column is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } async fn drop_column( - self, + &mut self, _table_name: &str, _column_name: &str, _if_exists: bool, - ) -> MutResult { + ) -> Result<()> { let msg = "[Storage] AlterTable::drop_column is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } } diff --git a/core/src/store/index.rs b/core/src/store/index.rs index 0c65c1bae..102085b8d 100644 --- a/core/src/store/index.rs +++ b/core/src/store/index.rs @@ -3,7 +3,7 @@ use { crate::{ ast::{IndexOperator, OrderByExpr}, data::Value, - result::{Error, MutResult, Result}, + result::{Error, Result}, }, async_trait::async_trait, serde::Serialize, @@ -54,24 +54,21 @@ pub trait Index { } #[async_trait(?Send)] -pub trait IndexMut -where - Self: Sized, -{ +pub trait IndexMut { async fn create_index( - self, + &mut self, _table_name: &str, _index_name: &str, _column: &OrderByExpr, - ) -> MutResult { + ) -> Result<()> { let msg = "[Storage] Index::create_index is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } - async fn drop_index(self, _table_name: &str, _index_name: &str) -> MutResult { + async fn drop_index(&mut self, _table_name: &str, _index_name: &str) -> Result<()> { let msg = "[Storage] Index::drop_index is not supported".to_owned(); - Err((self, Error::StorageMsg(msg))) + Err(Error::StorageMsg(msg)) } } diff --git a/core/src/store/mod.rs b/core/src/store/mod.rs index cc6d6015e..33109ae13 100644 --- a/core/src/store/mod.rs +++ b/core/src/store/mod.rs @@ -46,7 +46,7 @@ cfg_if! { impl GStoreMut for S {} } else if #[cfg(feature = "alter-table")] { pub trait GStoreMut: StoreMut + AlterTable {} - impl GStoreMut for S {} + impl GStoreMut for S {} } else if #[cfg(feature = "index")] { pub trait GStoreMut: StoreMut + IndexMut {} impl GStoreMut for S {} @@ -65,7 +65,7 @@ pub use data_row::DataRow; use { crate::{ data::{Key, Schema}, - result::{MutResult, Result}, + result::Result, }, async_trait::async_trait, }; @@ -87,17 +87,14 @@ pub trait Store { /// By implementing `StoreMut` trait, /// you can run `INSERT`, `CREATE TABLE`, `DELETE`, `UPDATE` and `DROP TABLE` queries. #[async_trait(?Send)] -pub trait StoreMut -where - Self: Sized, -{ - async fn insert_schema(self, schema: &Schema) -> MutResult; +pub trait StoreMut { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()>; - async fn delete_schema(self, table_name: &str) -> MutResult; + async fn delete_schema(&mut self, table_name: &str) -> Result<()>; - async fn append_data(self, table_name: &str, rows: Vec) -> MutResult; + async fn append_data(&mut self, table_name: &str, rows: Vec) -> Result<()>; - async fn insert_data(self, table_name: &str, rows: Vec<(Key, DataRow)>) -> MutResult; + async fn insert_data(&mut self, table_name: &str, rows: Vec<(Key, DataRow)>) -> Result<()>; - async fn delete_data(self, table_name: &str, keys: Vec) -> MutResult; + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()>; } diff --git a/core/src/store/transaction.rs b/core/src/store/transaction.rs index 74395eab1..72805df54 100644 --- a/core/src/store/transaction.rs +++ b/core/src/store/transaction.rs @@ -1,35 +1,25 @@ use { - crate::result::{Error, MutResult}, + crate::result::{Error, Result}, async_trait::async_trait, }; #[async_trait(?Send)] -pub trait Transaction -where - Self: Sized, -{ - async fn begin(self, autocommit: bool) -> MutResult { +pub trait Transaction { + async fn begin(&mut self, autocommit: bool) -> Result { if autocommit { - return Ok((self, false)); + return Ok(false); } - Err(( - self, - Error::StorageMsg("[Storage] Transaction::begin is not supported".to_owned()), + Err(Error::StorageMsg( + "[Storage] Transaction::begin is not supported".to_owned(), )) } - async fn rollback(self) -> MutResult { - Err(( - self, - Error::StorageMsg("[Storage] Transaction::rollback is not supported".to_owned()), - )) + async fn rollback(&mut self) -> Result<()> { + Ok(()) } - async fn commit(self) -> MutResult { - Err(( - self, - Error::StorageMsg("[Storage] Transaction::commit is not supported".to_owned()), - )) + async fn commit(&mut self) -> Result<()> { + Ok(()) } } diff --git a/core/src/translate/data_type.rs b/core/src/translate/data_type.rs index 47d87e7ce..1ba2e8fa2 100644 --- a/core/src/translate/data_type.rs +++ b/core/src/translate/data_type.rs @@ -35,6 +35,7 @@ pub fn translate_data_type(sql_data_type: &SqlDataType) -> Result { Some("UINT32") => Ok(DataType::Uint32), Some("UINT64") => Ok(DataType::Uint64), Some("UINT128") => Ok(DataType::Uint128), + Some("INET") => Ok(DataType::Inet), _ => Err(TranslateError::UnsupportedDataType(sql_data_type.to_string()).into()), } diff --git a/core/src/translate/function.rs b/core/src/translate/function.rs index 1292e57e5..a5ed55159 100644 --- a/core/src/translate/function.rs +++ b/core/src/translate/function.rs @@ -311,6 +311,15 @@ pub fn translate_function(sql_function: &SqlFunction) -> Result { fill, }))) } + "RAND" => { + check_len_range(name, args.len(), 0, 1)?; + let v = if args.is_empty() { + None + } else { + Some(translate_expr(args[0])?) + }; + Ok(Expr::Function(Box::new(Function::Rand(v)))) + } "ROUND" => translate_function_one_arg(Function::Round, args, name), "EXP" => translate_function_one_arg(Function::Exp, args, name), "LN" => translate_function_one_arg(Function::Ln, args, name), diff --git a/core/src/translate/mod.rs b/core/src/translate/mod.rs index 751814ed4..0972bb1b1 100644 --- a/core/src/translate/mod.rs +++ b/core/src/translate/mod.rs @@ -71,19 +71,27 @@ pub fn translate(sql_statement: &SqlStatement) -> Result { name, columns, query, + engine, .. - } => Ok(Statement::CreateTable { - if_not_exists: *if_not_exists, - name: translate_object_name(name)?, - columns: columns + } => { + let columns = columns .iter() .map(translate_column_def) - .collect::>()?, - source: match query { - Some(v) => Some(translate_query(v).map(Box::new)?), - None => None, - }, - }), + .collect::>>()?; + + let columns = (!columns.is_empty()).then_some(columns); + + Ok(Statement::CreateTable { + if_not_exists: *if_not_exists, + name: translate_object_name(name)?, + columns, + source: match query { + Some(v) => Some(translate_query(v).map(Box::new)?), + None => None, + }, + engine: engine.clone(), + }) + } #[cfg(feature = "alter-table")] SqlStatement::AlterTable { name, operation, .. diff --git a/pkg/javascript/examples/nodejs/main.js b/pkg/javascript/examples/nodejs/main.js index 72933a355..f07cbb8c2 100644 --- a/pkg/javascript/examples/nodejs/main.js +++ b/pkg/javascript/examples/nodejs/main.js @@ -6,10 +6,10 @@ async function run() { CREATE TABLE User (id INTEGER, name TEXT); CREATE TABLE Device (name TEXT, userId INTEGER); INSERT INTO User VALUES - (1, "glue"), (2, "sticky"), (3, "watt"); + (1, 'glue'), (2, 'sticky'), (3, 'watt'); INSERT INTO Device VALUES - ("Phone", 1), ("Mic", 1), ("Monitor", 3), - ("Mouse", 2), ("Touchpad", 2); + ('Phone', 1), ('Mic', 1), ('Monitor', 3), + ('Mouse', 2), ('Touchpad', 2); `); let sql; diff --git a/pkg/javascript/examples/web/module/index.html b/pkg/javascript/examples/web/module/index.html index 605c2c85d..875ad5b18 100644 --- a/pkg/javascript/examples/web/module/index.html +++ b/pkg/javascript/examples/web/module/index.html @@ -10,11 +10,16 @@ async function run() { const db = await gluesql(); + await db.loadIndexedDB(); const result = await db.query(` - CREATE TABLE Foo (id INTEGER, name TEXT); - INSERT INTO Foo VALUES (1, "hello"), (2, "world"); + DROP TABLE IF EXISTS Foo, Bar; + CREATE TABLE Foo (id INTEGER, name TEXT) ENGINE = indexedDB; + INSERT INTO Foo VALUES (1, 'hello'), (2, 'world'); SELECT *, id as wow_id FROM Foo; + CREATE TABLE Bar ENGINE = indexedDB; + INSERT INTO Bar VALUES ('{ "a": "schemaless", "b": 1024 }'); + SELECT * FROM Bar; `); for (const item of result) { diff --git a/pkg/javascript/examples/web/rollup/main.js b/pkg/javascript/examples/web/rollup/main.js index d1ace4254..1cc625f7d 100644 --- a/pkg/javascript/examples/web/rollup/main.js +++ b/pkg/javascript/examples/web/rollup/main.js @@ -2,11 +2,15 @@ import { gluesql } from 'gluesql/gluesql.rollup'; async function run() { const db = await gluesql(); + await db.loadIndexedDB(); const result = await db.query(` + DROP TABLE IF EXISTS Foo, Bar; CREATE TABLE Foo (id INTEGER, name TEXT); - INSERT INTO Foo VALUES (1, "hello"), (2, "world"); - SELECT *, id as wow_id FROM Foo; + CREATE TABLE Bar (bar_id INTEGER) ENGINE = indexedDB; + INSERT INTO Foo VALUES (1, 'hello'), (2, 'world'); + INSERT INTO Bar VALUES (10), (20); + SELECT *, id as wow_id FROM Foo JOIN Bar; `); for (const item of result) { diff --git a/pkg/javascript/examples/web/rollup/package.json b/pkg/javascript/examples/web/rollup/package.json index 53911a95d..d100de45a 100644 --- a/pkg/javascript/examples/web/rollup/package.json +++ b/pkg/javascript/examples/web/rollup/package.json @@ -14,11 +14,11 @@ }, "homepage": "https://github.com/gluesql/gluesql#readme", "scripts": { - "build": "rollup -c" + "build": "yarn upgrade gluesql && rollup -c" }, "devDependencies": { "@rollup/plugin-node-resolve": "^13.2.1", - "gluesql": "file:../../../", + "gluesql": "file:../../..", "rollup": "^2.70.2" } } diff --git a/pkg/javascript/examples/web/webpack/main.js b/pkg/javascript/examples/web/webpack/main.js index c4eda1711..4873f6247 100644 --- a/pkg/javascript/examples/web/webpack/main.js +++ b/pkg/javascript/examples/web/webpack/main.js @@ -2,10 +2,12 @@ import { gluesql } from 'gluesql'; async function run() { const db = await gluesql(); + await db.loadIndexedDB(); const result = await db.query(` - CREATE TABLE Foo (id INTEGER, name TEXT); - INSERT INTO Foo VALUES (1, "hello"), (2, "world"); + DROP TABLE IF EXISTS Foo; + CREATE TABLE Foo (id INTEGER, name TEXT) ENGINE = indexedDB; + INSERT INTO Foo VALUES (1, 'hello'), (2, 'world'); SELECT *, id as wow_id FROM Foo; `); diff --git a/pkg/javascript/examples/web/webpack/package.json b/pkg/javascript/examples/web/webpack/package.json index e528b9b97..107102c41 100644 --- a/pkg/javascript/examples/web/webpack/package.json +++ b/pkg/javascript/examples/web/webpack/package.json @@ -14,10 +14,10 @@ }, "homepage": "https://github.com/gluesql/gluesql#readme", "scripts": { - "build": "webpack" + "build": "yarn upgrade gluesql && webpack" }, "devDependencies": { - "gluesql": "file:../../../", + "gluesql": "file:../../..", "webpack": "^5.72.0", "webpack-cli": "^4.9.2" } diff --git a/pkg/javascript/web/Cargo.toml b/pkg/javascript/web/Cargo.toml index c3d95825c..e6fc9c26f 100644 --- a/pkg/javascript/web/Cargo.toml +++ b/pkg/javascript/web/Cargo.toml @@ -14,6 +14,8 @@ crate-type = ["cdylib", "rlib"] [features] default = ["console_error_panic_hook"] +nodejs = ["console_error_panic_hook"] + [dependencies] wasm-bindgen = { version = "0.2.79" } wasm-bindgen-futures = "0.4.29" @@ -31,6 +33,9 @@ console_error_panic_hook = { version = "0.1.6", optional = true } gluesql-core = { path = "../../../core", version = "0.13.1" } memory-storage = { package = "gluesql_memory_storage", path = "../../../storages/memory-storage", version = "0.13.0" } +web-storage = { package = "gluesql-web-storage", path = "../../../storages/web-storage", version = "0.13.0" } +idb-storage = { package = "gluesql-idb-storage", path = "../../../storages/idb-storage", version = "0.13.0" } +composite-storage = { package = "gluesql-composite-storage", path = "../../../storages/composite-storage", version = "0.13.0" } [dev-dependencies] wasm-bindgen-test = "0.3.13" @@ -40,4 +45,3 @@ async-trait = "0.1" package = "gluesql-test-suite" path = "../../../test-suite" version = "0.13.1" -features = ["alter-table"] diff --git a/pkg/javascript/web/package.json b/pkg/javascript/web/package.json index 3631df16d..0b75f5742 100644 --- a/pkg/javascript/web/package.json +++ b/pkg/javascript/web/package.json @@ -7,7 +7,7 @@ "scripts": { "build:rollup": "yarn rollup -c && cp ../dist/bundler/gluesql.js ../gluesql.rollup.js", "build:browser": "wasm-pack build --target web --no-typescript --release --out-dir ../dist/web", - "build:nodejs": "wasm-pack build --target nodejs --no-typescript --release --out-dir ../dist/nodejs", + "build:nodejs": "wasm-pack build --target nodejs --no-typescript --release --out-dir ../dist/nodejs -- --no-default-features --features nodejs", "build": "yarn run build:rollup && yarn run build:browser && yarn run build:nodejs", "test:firefox": "wasm-pack test --headless --firefox", "test:chrome": "wasm-pack test --headless --chrome", diff --git a/pkg/javascript/web/src/lib.rs b/pkg/javascript/web/src/lib.rs index 901f97384..f54a7b99f 100644 --- a/pkg/javascript/web/src/lib.rs +++ b/pkg/javascript/web/src/lib.rs @@ -13,6 +13,13 @@ use { wasm_bindgen_futures::future_to_promise, }; +#[cfg(not(feature = "nodejs"))] +use { + composite_storage::CompositeStorage, + idb_storage::IdbStorage, + web_storage::{WebStorage, WebStorageType}, +}; + #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] @@ -21,6 +28,10 @@ extern "C" { #[wasm_bindgen] pub struct Glue { + #[cfg(not(feature = "nodejs"))] + storage: Rc>>, + + #[cfg(feature = "nodejs")] storage: Rc>>, } @@ -37,14 +48,89 @@ impl Glue { pub fn new() -> Self { utils::set_panic_hook(); - let storage = Some(MemoryStorage::default()); - let storage = Rc::new(RefCell::new(storage)); + #[cfg(not(feature = "nodejs"))] + let storage = { + let mut storage = CompositeStorage::default(); + storage.push("memory", MemoryStorage::default()); + storage.push("localStorage", WebStorage::new(WebStorageType::Local)); + storage.push("sessionStorage", WebStorage::new(WebStorageType::Session)); + storage.set_default("memory"); + log("[GlueSQL] loaded: memory, localStorage, sessionStorage"); + log("[GlueSQL] default engine: memory"); + + storage + }; + #[cfg(feature = "nodejs")] + let storage = MemoryStorage::default(); + + let storage = Rc::new(RefCell::new(Some(storage))); log("[GlueSQL] hello :)"); Self { storage } } + #[cfg(not(feature = "nodejs"))] + #[wasm_bindgen(js_name = loadIndexedDB)] + pub fn load_indexeddb(&mut self) -> Promise { + let cell = Rc::clone(&self.storage); + + future_to_promise(async move { + let mut storage = cell.replace(None).unwrap(); + + if storage.storages.contains_key("indexedDB") { + cell.replace(Some(storage)); + + return Err(JsValue::from_str("indexedDB storage is already loaded")); + } + + let idb_storage = match IdbStorage::new(None).await { + Ok(storage) => storage, + Err(error) => { + cell.replace(Some(storage)); + + return Err(JsValue::from_str(&format!("{error}"))); + } + }; + + storage.push("indexedDB", idb_storage); + log("[GlueSQL] loaded: indexedDB"); + + cell.replace(Some(storage)); + + Ok(JsValue::NULL) + }) + } + + #[cfg(not(feature = "nodejs"))] + #[wasm_bindgen(js_name = setDefaultEngine)] + pub fn set_default_engine(&mut self, default_engine: String) -> Result<(), JsValue> { + let cell = Rc::clone(&self.storage); + let mut storage = cell.replace(None).unwrap(); + + let result = { + if !["memory", "localStorage", "sessionStorage", "indexedDB"] + .iter() + .any(|engine| engine == &default_engine.as_str()) + { + Err(JsValue::from_str( + format!("{default_engine} is not supported (options: memory, localStorage, sessionStorage, indexedDB)").as_str() + )) + } else if default_engine == "indexedDB" && !storage.storages.contains_key("indexedDB") { + Err(JsValue::from_str( + "indexedDB is not loaded - run loadIndexedDB() first", + )) + } else { + storage.set_default(default_engine); + + Ok(()) + } + }; + + cell.replace(Some(storage)); + result + } + pub fn query(&mut self, sql: String) -> Promise { let cell = Rc::clone(&self.storage); @@ -52,7 +138,7 @@ impl Glue { let queries = parse(&sql).map_err(|error| JsValue::from_str(&format!("{error}")))?; let mut payloads = vec![]; - let mut storage: MemoryStorage = cell.replace(None).unwrap(); + let mut storage = cell.replace(None).unwrap(); for query in queries.iter() { let statement = translate(query); @@ -74,17 +160,15 @@ impl Glue { } }; - let result = execute(storage, &statement) + let result = execute(&mut storage, &statement) .await - .map_err(|(storage, error)| (storage, JsValue::from_str(&format!("{error}")))); + .map_err(|error| JsValue::from_str(&format!("{error}"))); match result { - Ok((s, payload)) => { - storage = s; - + Ok(payload) => { payloads.push(payload); } - Err((storage, error)) => { + Err(error) => { cell.replace(Some(storage)); return Err(error); diff --git a/pkg/javascript/web/tests/composite_storage.rs b/pkg/javascript/web/tests/composite_storage.rs new file mode 100644 index 000000000..dd2002ab1 --- /dev/null +++ b/pkg/javascript/web/tests/composite_storage.rs @@ -0,0 +1,31 @@ +#![cfg(target_arch = "wasm32")] + +use { + async_trait::async_trait, composite_storage::CompositeStorage, gluesql_core::prelude::Glue, + memory_storage::MemoryStorage, test_suite::*, wasm_bindgen_test::*, +}; + +wasm_bindgen_test_configure!(run_in_browser); + +struct CompositeTester { + glue: Glue, +} + +#[async_trait(?Send)] +impl Tester for CompositeTester { + async fn new(_: &str) -> Self { + let mut storage = CompositeStorage::default(); + storage.push("memory", MemoryStorage::default()); + storage.set_default("memory"); + + let glue = Glue::new(storage); + + Self { glue } + } + + fn get_glue(&mut self) -> &mut Glue { + &mut self.glue + } +} + +generate_store_tests!(wasm_bindgen_test, CompositeTester); diff --git a/pkg/javascript/web/tests/error.rs b/pkg/javascript/web/tests/error.rs new file mode 100644 index 000000000..e478f42d6 --- /dev/null +++ b/pkg/javascript/web/tests/error.rs @@ -0,0 +1,60 @@ +#![cfg(all(target_arch = "wasm32", not(feature = "nodejs")))] + +wasm_bindgen_test_configure!(run_in_browser); + +use { + gloo_utils::format::JsValueSerdeExt, + gluesql_js::Glue, + serde_json::{json, Value as Json}, + wasm_bindgen::prelude::JsValue, + wasm_bindgen_futures::JsFuture, + wasm_bindgen_test::*, +}; + +#[wasm_bindgen_test] +async fn error() { + let mut glue = Glue::new(); + + assert_eq!( + glue.set_default_engine("something-else".to_owned()), + Err(JsValue::from_str( + "something-else is not supported (options: memory, localStorage, sessionStorage, indexedDB)", + )) + ); + + assert_eq!( + glue.set_default_engine("indexedDB".to_owned()), + Err(JsValue::from_str( + "indexedDB is not loaded - run loadIndexedDB() first", + )) + ); + + let loaded = glue.load_indexeddb(); + JsFuture::from(loaded).await.unwrap(); + assert_eq!(glue.set_default_engine("indexedDB".to_owned()), Ok(())); + + let loaded = glue.load_indexeddb(); + assert_eq!( + JsFuture::from(loaded).await, + Err(JsValue::from_str("indexedDB storage is already loaded")) + ); + + let sql = " + CREATE TABLE Mem (mid INTEGER) ENGINE = memory; + CREATE TABLE Loc (lid INTEGER) ENGINE = localStorage; + CREATE TABLE Ses (sid INTEGER) ENGINE = sessionStorage; + CREATE TABLE Idb (iid INTEGER) ENGINE = indexedDB; + "; + let actual: Json = JsFuture::from(glue.query(sql.to_owned())) + .await + .unwrap() + .into_serde() + .unwrap(); + let expected = json!([ + { "type": "CREATE TABLE" }, + { "type": "CREATE TABLE" }, + { "type": "CREATE TABLE" }, + { "type": "CREATE TABLE" } + ]); + assert_eq!(actual, expected); +} diff --git a/pkg/javascript/web/tests/join_multiple_storages.rs b/pkg/javascript/web/tests/join_multiple_storages.rs new file mode 100644 index 000000000..ea6037c2b --- /dev/null +++ b/pkg/javascript/web/tests/join_multiple_storages.rs @@ -0,0 +1,92 @@ +#![cfg(all(target_arch = "wasm32", not(feature = "nodejs")))] + +wasm_bindgen_test_configure!(run_in_browser); + +use { + gloo_utils::format::JsValueSerdeExt, + gluesql_js::Glue, + serde_json::{json, Value as Json}, + wasm_bindgen_futures::JsFuture, + wasm_bindgen_test::*, +}; + +#[wasm_bindgen_test] +async fn join_multiple_storages() { + let mut glue = Glue::new(); + let loaded = glue.load_indexeddb(); + JsFuture::from(loaded).await.unwrap(); + + let mut test = |sql: &'static str, expected| { + let result = glue.query(sql.to_owned()); + + async move { + let actual: Json = JsFuture::from(result).await.unwrap().into_serde().unwrap(); + + assert_eq!(actual, expected, "{sql}"); + } + }; + + test( + " + CREATE TABLE Mem (mid INTEGER) ENGINE = memory; + CREATE TABLE Loc (lid INTEGER) ENGINE = localStorage; + CREATE TABLE Ses (sid INTEGER) ENGINE = sessionStorage; + CREATE TABLE Idb (iid INTEGER) ENGINE = indexedDB; + ", + json!([ + { "type": "CREATE TABLE" }, + { "type": "CREATE TABLE" }, + { "type": "CREATE TABLE" }, + { "type": "CREATE TABLE" } + ]), + ) + .await; + + test( + " + INSERT INTO Mem VALUES (10), (11); + INSERT INTO Loc VALUES (20), (21); + INSERT INTO Ses VALUES (30), (31); + INSERT INTO Idb VALUES (40), (41); + ", + json!([ + { "type": "INSERT", "affected": 2 }, + { "type": "INSERT", "affected": 2 }, + { "type": "INSERT", "affected": 2 }, + { "type": "INSERT", "affected": 2 } + ]), + ) + .await; + + test( + " + SELECT mid, lid, sid, iid + FROM Mem + JOIN Loc + JOIN Ses + JOIN Idb; + ", + json!([{ + "type": "SELECT", + "rows": [ + { "mid": 10, "lid": 20, "sid": 30, "iid": 40 }, + { "mid": 10, "lid": 20, "sid": 30, "iid": 41 }, + { "mid": 10, "lid": 20, "sid": 31, "iid": 40 }, + { "mid": 10, "lid": 20, "sid": 31, "iid": 41 }, + { "mid": 10, "lid": 21, "sid": 30, "iid": 40 }, + { "mid": 10, "lid": 21, "sid": 30, "iid": 41 }, + { "mid": 10, "lid": 21, "sid": 31, "iid": 40 }, + { "mid": 10, "lid": 21, "sid": 31, "iid": 41 }, + { "mid": 11, "lid": 20, "sid": 30, "iid": 40 }, + { "mid": 11, "lid": 20, "sid": 30, "iid": 41 }, + { "mid": 11, "lid": 20, "sid": 31, "iid": 40 }, + { "mid": 11, "lid": 20, "sid": 31, "iid": 41 }, + { "mid": 11, "lid": 21, "sid": 30, "iid": 40 }, + { "mid": 11, "lid": 21, "sid": 30, "iid": 41 }, + { "mid": 11, "lid": 21, "sid": 31, "iid": 40 }, + { "mid": 11, "lid": 21, "sid": 31, "iid": 41 } + ] + }]), + ) + .await; +} diff --git a/pkg/javascript/web/tests/memory_storage.rs b/pkg/javascript/web/tests/memory_storage.rs deleted file mode 100644 index a88afb691..000000000 --- a/pkg/javascript/web/tests/memory_storage.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![cfg(target_arch = "wasm32")] - -use { - async_trait::async_trait, gluesql_core::prelude::Glue, memory_storage::MemoryStorage, - test_suite::*, wasm_bindgen_test::*, -}; - -wasm_bindgen_test_configure!(run_in_browser); - -struct MemoryTester { - glue: Glue, -} - -#[async_trait(?Send)] -impl Tester for MemoryTester { - async fn new(_: &str) -> Self { - let storage = MemoryStorage::default(); - let glue = Glue::new(storage); - - MemoryTester { glue } - } - - fn get_glue(&mut self) -> &mut Glue { - &mut self.glue - } -} - -generate_store_tests!(wasm_bindgen_test, MemoryTester); -generate_alter_table_tests!(wasm_bindgen_test, MemoryTester); diff --git a/pkg/rust/Cargo.toml b/pkg/rust/Cargo.toml index 09967516b..17340512c 100644 --- a/pkg/rust/Cargo.toml +++ b/pkg/rust/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "gluesql" version = "0.13.1" -edition = "2021" authors = ["Taehoon Moon "] default-run = "gluesql" -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true readme = "../../README.md" keywords = [ "sql-database", diff --git a/pkg/rust/examples/api_usage.rs b/pkg/rust/examples/api_usage.rs index 93673a322..a445132f5 100644 --- a/pkg/rust/examples/api_usage.rs +++ b/pkg/rust/examples/api_usage.rs @@ -2,30 +2,9 @@ mod api_usage { use { futures::executor::block_on, - gluesql::prelude::{execute, parse, translate, Glue, SledStorage}, + gluesql::prelude::{Glue, SledStorage}, }; - fn immutable_api() { - let storage = SledStorage::new("data/immutable-api").unwrap(); - - let sqls = " - CREATE TABLE Glue (id INTEGER); - INSERT INTO Glue VALUES (100); - INSERT INTO Glue VALUES (200); - DROP TABLE Glue; - "; - - parse(sqls) - .unwrap() - .iter() - .fold(storage, |storage, parsed| { - let statement = translate(parsed).unwrap(); - let (storage, _) = block_on(execute(storage, &statement)).unwrap(); - - storage - }); - } - fn mutable_api() { let storage = SledStorage::new("data/mutable-api").unwrap(); let mut glue = Glue::new(storage); @@ -60,7 +39,6 @@ mod api_usage { pub fn run() { mutable_api(); - immutable_api(); block_on(async_mutable_api()); } } diff --git a/storages/composite-storage/Cargo.toml b/storages/composite-storage/Cargo.toml new file mode 100644 index 000000000..9fc0ffbfe --- /dev/null +++ b/storages/composite-storage/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "gluesql-composite-storage" +version = "0.13.0" +authors = ["Taehoon Moon "] +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true + +[dependencies] +gluesql-core = { path = "../../core", version = "0.13.0" } +async-trait = "0.1" +serde = { version = "1", features = ["derive"] } +futures = "0.3" + +[dev-dependencies] +test-suite = { package = "gluesql-test-suite", path = "../../test-suite", version = "0.13.0" } +gluesql_memory_storage = { path = "../memory-storage", version = "0.13.0", default-features = false } +gluesql_sled_storage = { path = "../sled-storage", version = "0.13.0" } +tokio = { version = "1", features = ["rt", "macros"] } + +[features] +default = ["alter-table", "index", "transaction"] + +alter-table = ["gluesql-core/alter-table", "test-suite/alter-table", "gluesql_memory_storage/alter-table"] +index = ["gluesql-core/index", "test-suite/index", "gluesql_memory_storage/index"] +transaction = ["gluesql-core/transaction", "test-suite/transaction", "gluesql_memory_storage/transaction"] diff --git a/storages/composite-storage/src/lib.rs b/storages/composite-storage/src/lib.rs new file mode 100644 index 000000000..517d666f8 --- /dev/null +++ b/storages/composite-storage/src/lib.rs @@ -0,0 +1,94 @@ +#![deny(clippy::str_to_string)] + +mod store; +mod store_mut; +mod transaction; + +use { + gluesql_core::{ + data::Schema, + result::{Error, Result}, + store::{GStore, GStoreMut, Store}, + }, + std::collections::HashMap, +}; + +pub trait IStorage: GStore + GStoreMut {} + +impl IStorage for T {} + +#[derive(Default)] +pub struct CompositeStorage { + pub storages: HashMap>, + pub default_engine: Option, +} + +impl CompositeStorage { + pub fn new() -> Self { + CompositeStorage::default() + } + + pub fn set_default>(&mut self, default_engine: T) { + self.default_engine = Some(default_engine.into()); + } + + pub fn remove_default(&mut self) { + self.default_engine = None; + } + + pub fn push, U: IStorage + 'static>(&mut self, engine: T, storage: U) { + self.storages.insert(engine.into(), Box::new(storage)); + } + + pub fn remove>(&mut self, engine: T) -> Option> { + let engine = engine.as_ref(); + + if self.default_engine.as_deref() == Some(engine) { + self.default_engine = None; + } + + self.storages.remove(engine) + } + + pub fn clear(&mut self) { + self.storages.clear(); + self.default_engine = None; + } + + async fn fetch_engine(&self, table_name: &str) -> Result { + self.fetch_schema(table_name) + .await? + .and_then(|Schema { engine, .. }| engine) + .or_else(|| self.default_engine.clone()) + .ok_or_else(|| Error::StorageMsg(format!("engine not found for table: {table_name}"))) + } + + async fn fetch_storage(&self, table_name: &str) -> Result<&Box> { + self.fetch_engine(table_name) + .await + .map(|engine| self.storages.get(&engine))? + .ok_or_else(|| { + Error::StorageMsg(format!( + "[fetch_storage] storage not found for table: {table_name}" + )) + }) + } + + async fn fetch_storage_mut(&mut self, table_name: &str) -> Result<&mut Box> { + self.fetch_engine(table_name) + .await + .map(|engine| self.storages.get_mut(&engine))? + .ok_or_else(|| { + Error::StorageMsg(format!( + "[fetch_storage_mut] storage not found for table: {table_name}" + )) + }) + } +} + +#[cfg(feature = "alter-table")] +impl gluesql_core::store::AlterTable for CompositeStorage {} +#[cfg(feature = "index")] +impl gluesql_core::store::Index for CompositeStorage {} +#[cfg(feature = "index")] +impl gluesql_core::store::IndexMut for CompositeStorage {} diff --git a/storages/composite-storage/src/store.rs b/storages/composite-storage/src/store.rs new file mode 100644 index 000000000..b25058a82 --- /dev/null +++ b/storages/composite-storage/src/store.rs @@ -0,0 +1,52 @@ +use { + super::{CompositeStorage, IStorage}, + async_trait::async_trait, + futures::stream::{self, StreamExt, TryStreamExt}, + gluesql_core::{ + data::{Key, Schema}, + result::Result, + store::{DataRow, RowIter, Store}, + }, +}; + +#[async_trait(?Send)] +impl Store for CompositeStorage { + async fn fetch_all_schemas(&self) -> Result> { + let schemas = stream::iter(self.storages.values()) + .map(AsRef::as_ref) + .then(::fetch_all_schemas) + .try_collect::>() + .await? + .into_iter() + .flatten() + .collect(); + + Ok(schemas) + } + + async fn fetch_schema(&self, table_name: &str) -> Result> { + for storage in self.storages.values() { + let schema = storage.fetch_schema(table_name).await?; + + if schema.is_some() { + return Ok(schema); + } + } + + Ok(None) + } + + async fn fetch_data(&self, table_name: &str, key: &Key) -> Result> { + self.fetch_storage(table_name) + .await? + .fetch_data(table_name, key) + .await + } + + async fn scan_data(&self, table_name: &str) -> Result { + self.fetch_storage(table_name) + .await? + .scan_data(table_name) + .await + } +} diff --git a/storages/composite-storage/src/store_mut.rs b/storages/composite-storage/src/store_mut.rs new file mode 100644 index 000000000..260bef9db --- /dev/null +++ b/storages/composite-storage/src/store_mut.rs @@ -0,0 +1,64 @@ +use { + super::CompositeStorage, + async_trait::async_trait, + gluesql_core::{ + data::{Key, Schema}, + result::{Error, Result}, + store::{DataRow, StoreMut}, + }, +}; + +#[async_trait(?Send)] +impl StoreMut for CompositeStorage { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { + let storage = schema + .engine + .as_ref() + .or(self.default_engine.as_ref()) + .and_then(|engine| self.storages.get_mut(engine)); + + match (storage, schema.engine.is_some()) { + (Some(storage), true) => storage.insert_schema(schema).await, + (Some(storage), false) => { + let schema = Schema { + engine: self.default_engine.clone(), + ..schema.clone() + }; + + storage.insert_schema(&schema).await + } + (None, _) => Err(Error::StorageMsg(format!( + "storage not found for table: {}", + schema.table_name + ))), + } + } + + async fn delete_schema(&mut self, table_name: &str) -> Result<()> { + self.fetch_storage_mut(table_name) + .await? + .delete_schema(table_name) + .await + } + + async fn append_data(&mut self, table_name: &str, rows: Vec) -> Result<()> { + self.fetch_storage_mut(table_name) + .await? + .append_data(table_name, rows) + .await + } + + async fn insert_data(&mut self, table_name: &str, rows: Vec<(Key, DataRow)>) -> Result<()> { + self.fetch_storage_mut(table_name) + .await? + .insert_data(table_name, rows) + .await + } + + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { + self.fetch_storage_mut(table_name) + .await? + .delete_data(table_name, keys) + .await + } +} diff --git a/storages/composite-storage/src/transaction.rs b/storages/composite-storage/src/transaction.rs new file mode 100644 index 000000000..db30f0887 --- /dev/null +++ b/storages/composite-storage/src/transaction.rs @@ -0,0 +1,43 @@ +#![cfg(feature = "transaction")] + +use { + super::CompositeStorage, + async_trait::async_trait, + gluesql_core::{ + result::{Error, Result}, + store::Transaction, + }, +}; + +#[async_trait(?Send)] +impl Transaction for CompositeStorage { + async fn begin(&mut self, autocommit: bool) -> Result { + if autocommit { + for storage in self.storages.values_mut() { + storage.begin(autocommit).await?; + } + + return Ok(true); + } + + Err(Error::StorageMsg( + "[CompositeStorage] Transaction::begin is not supported".to_owned(), + )) + } + + async fn rollback(&mut self) -> Result<()> { + for storage in self.storages.values_mut() { + storage.commit().await?; + } + + Ok(()) + } + + async fn commit(&mut self) -> Result<()> { + for storage in self.storages.values_mut() { + storage.commit().await?; + } + + Ok(()) + } +} diff --git a/storages/composite-storage/tests/basic.rs b/storages/composite-storage/tests/basic.rs new file mode 100644 index 000000000..ffbc1b407 --- /dev/null +++ b/storages/composite-storage/tests/basic.rs @@ -0,0 +1,75 @@ +use { + gluesql_composite_storage::CompositeStorage, + gluesql_core::{ + executor::FetchError, + prelude::{Glue, Value::I64}, + result::Error, + }, + gluesql_memory_storage::MemoryStorage, + test_suite::*, +}; + +#[test] +fn basic() { + let m1 = MemoryStorage::default(); + let m2 = MemoryStorage::default(); + + let mut storage = CompositeStorage::new(); + storage.push("M1", m1); + storage.push("M2", m2); + + let mut glue = Glue::new(storage); + + glue.storage.set_default("M1"); + glue.execute("CREATE TABLE Foo (id INTEGER);").unwrap(); + + glue.storage.set_default("M2"); + glue.execute("CREATE TABLE Bar (id INTEGER);").unwrap(); + + glue.execute("INSERT INTO Foo VALUES (1), (2);").unwrap(); + glue.execute("INSERT INTO Bar VALUES (5), (7);").unwrap(); + + assert_eq!( + glue.execute( + "SELECT + Foo.id AS fid, + Bar.id AS bid + FROM Foo + JOIN Bar; + " + ) + .unwrap() + .into_iter() + .next() + .unwrap(), + select!( + fid | bid; + I64 | I64; + 1 5; + 1 7; + 2 5; + 2 7 + ) + ); + + glue.storage.remove("M2"); + assert_eq!( + glue.execute("SELECT * FROM Bar;"), + Err(FetchError::TableNotFound("Bar".to_owned()).into()) + ); + + glue.storage.set_default("M1"); + glue.storage.remove_default(); + assert_eq!( + glue.execute("CREATE TABLE Tae (id INTEGER);"), + Err(Error::StorageMsg( + "storage not found for table: Tae".to_owned() + )) + ); + + glue.storage.clear(); + assert_eq!( + glue.execute("SELECT * FROM Foo;"), + Err(FetchError::TableNotFound("Foo".to_owned()).into()) + ); +} diff --git a/storages/composite-storage/tests/composite_storage.rs b/storages/composite-storage/tests/composite_storage.rs new file mode 100644 index 000000000..68c015ff8 --- /dev/null +++ b/storages/composite-storage/tests/composite_storage.rs @@ -0,0 +1,27 @@ +use { + async_trait::async_trait, gluesql_composite_storage::CompositeStorage, + gluesql_core::prelude::Glue, gluesql_memory_storage::MemoryStorage, test_suite::*, +}; + +struct CompositeTester { + glue: Glue, +} + +#[async_trait(?Send)] +impl Tester for CompositeTester { + async fn new(_: &str) -> Self { + let mut storage = CompositeStorage::default(); + storage.push("MEMORY", MemoryStorage::default()); + storage.set_default("MEMORY"); + + let glue = Glue::new(storage); + + Self { glue } + } + + fn get_glue(&mut self) -> &mut Glue { + &mut self.glue + } +} + +generate_store_tests!(tokio::test, CompositeTester); diff --git a/storages/composite-storage/tests/error.rs b/storages/composite-storage/tests/error.rs new file mode 100644 index 000000000..12ce3269f --- /dev/null +++ b/storages/composite-storage/tests/error.rs @@ -0,0 +1,114 @@ +use { + futures::executor::block_on, + gluesql_composite_storage::CompositeStorage, + gluesql_core::{ + prelude::Glue, + result::Error, + store::{Store, StoreMut}, + }, + gluesql_memory_storage::MemoryStorage, +}; + +#[test] +fn error() { + let storage = CompositeStorage::new(); + + assert_eq!( + block_on(storage.scan_data("Foo")).map(|_| ()), + Err(Error::StorageMsg( + "engine not found for table: Foo".to_owned() + )) + ); + + let mut glue = Glue::new(storage); + assert_eq!( + glue.execute("CREATE TABLE Foo ENGINE = NONAME;"), + Err(Error::StorageMsg( + "storage not found for table: Foo".to_owned() + )) + ); + + let storage = { + let storage = MemoryStorage::default(); + let mut glue = Glue::new(storage); + glue.execute("CREATE TABLE WrongEngine (id INTEGER) ENGINE = SomethingElse") + .unwrap(); + + glue.storage + }; + + glue.storage.push("Test", storage); + glue.storage.set_default("Test"); + + glue.execute("CREATE TABLE Foo (id INTEGER);").unwrap(); + + assert_eq!( + block_on(glue.storage.scan_data("WrongEngine")).map(|_| ()), + Err(Error::StorageMsg( + "[fetch_storage] storage not found for table: WrongEngine".to_owned() + )) + ); + + assert_eq!( + block_on(glue.storage.delete_schema("WrongEngine")).map(|_| ()), + Err(Error::StorageMsg( + "[fetch_storage_mut] storage not found for table: WrongEngine".to_owned() + )) + ); +} + +#[cfg(any(feature = "alter-table", feature = "index"))] +macro_rules! exec { + ($glue: ident $sql: literal) => { + $glue.execute($sql).unwrap(); + }; +} + +#[cfg(any(feature = "alter-table", feature = "index"))] +macro_rules! test { + ($glue: ident $sql: literal, $result: expr) => { + assert_eq!($glue.execute($sql), $result); + }; +} + +#[cfg(feature = "index")] +#[test] +fn composite_storage_index() { + use { + gluesql_core::{result::Result, store::Index}, + gluesql_memory_storage::MemoryStorage, + }; + + let mut storage = CompositeStorage::default(); + storage.push("mem", MemoryStorage::default()); + storage.set_default("mem"); + + let mut glue = Glue::new(storage); + + exec!(glue "CREATE TABLE Idx (id INTEGER);"); + + assert_eq!( + block_on(glue.storage.scan_data("Idx")) + .unwrap() + .collect::>>() + .as_ref() + .map(Vec::len), + Ok(0), + ); + + assert_eq!( + block_on(glue.storage.scan_indexed_data("Idx", "hello", None, None)).map(|_| ()), + Err(Error::StorageMsg( + "[Storage] Index::scan_indexed_data is not supported".to_owned() + )) + ); + + test!( + glue "CREATE INDEX idx_id ON Idx (id);", + Err(Error::StorageMsg("[Storage] Index::create_index is not supported".to_owned())) + ); + test!( + glue "DROP INDEX Idx.idx_id;", + Err(Error::StorageMsg("[Storage] Index::drop_index is not supported".to_owned())) + ); +} diff --git a/storages/composite-storage/tests/memory_and_sled.rs b/storages/composite-storage/tests/memory_and_sled.rs new file mode 100644 index 000000000..10f81f4e8 --- /dev/null +++ b/storages/composite-storage/tests/memory_and_sled.rs @@ -0,0 +1,63 @@ +#![cfg(all(feature = "alter-table", feature = "index", feature = "transaction"))] + +use { + gluesql_composite_storage::CompositeStorage, + gluesql_core::{ + prelude::{Glue, Value::I64}, + result::Error, + }, + gluesql_memory_storage::MemoryStorage, + gluesql_sled_storage::SledStorage, + std::fs, + test_suite::*, +}; + +#[test] +fn memory_and_sled() { + let memory_storage = MemoryStorage::default(); + let sled_storage = { + let path = "data/memory_and_sled"; + fs::remove_dir_all(path).unwrap_or(()); + + SledStorage::new(path).unwrap() + }; + + let mut storage = CompositeStorage::new(); + storage.push("MEMORY", memory_storage); + storage.push("SLED", sled_storage); + storage.set_default("MEMORY"); + + let mut glue = Glue::new(storage); + + glue.execute("CREATE TABLE Foo (foo_id INTEGER) ENGINE = MEMORY;") + .unwrap(); + glue.execute("CREATE TABLE Bar (bar_id INTEGER, foo_id INTEGER) ENGINE = SLED;") + .unwrap(); + + glue.execute("INSERT INTO Foo VALUES (1), (2), (3), (4), (5);") + .unwrap(); + glue.execute("INSERT INTO Bar VALUES (10, 1), (20, 3), (30, 3), (40, 3), (50, 5);") + .unwrap(); + + assert_eq!( + glue.execute("SELECT Bar.* FROM Bar LEFT JOIN Foo ON Bar.foo_id = Foo.foo_id;") + .unwrap() + .into_iter() + .next() + .unwrap(), + select!( + bar_id | foo_id + I64 | I64; + 10 1; + 20 3; + 30 3; + 40 3; + 50 5 + ) + ); + + assert_eq!( + glue.execute("BEGIN;").unwrap_err(), + Error::StorageMsg("[CompositeStorage] Transaction::begin is not supported".to_owned()), + ); +} diff --git a/storages/idb-storage/Cargo.toml b/storages/idb-storage/Cargo.toml new file mode 100644 index 000000000..386634d96 --- /dev/null +++ b/storages/idb-storage/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "gluesql-idb-storage" +version = "0.13.0" +edition = "2021" +authors = ["Taehoon Moon "] +description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" +license = "Apache-2.0" +repository = "https://github.com/gluesql/gluesql" +documentation = "https://docs.rs/gluesql/" + +[dependencies] +gluesql-core = { path = "../../core", version = "0.13.0" } +async-trait = "0.1" +serde = { version = "1", features = ["derive"] } +idb = "0.4.0" +serde-wasm-bindgen = "0.4.5" +wasm-bindgen = "0.2.83" +web-sys = { version = "0.3.60", features = ["console"] } +serde_json = "1.0.91" +gloo-utils = { version = "0.1.6", features = ["serde"] } + +[dev-dependencies] +test-suite = { package = "gluesql-test-suite", path = "../../test-suite", version = "0.13.0" } +wasm-bindgen-test = "0.3.33" + +[features] +default = [ + "alter-table", + "index", + "transaction", +] + +alter-table = ["gluesql-core/alter-table"] +index = ["gluesql-core/index"] +transaction = ["gluesql-core/transaction"] diff --git a/storages/idb-storage/README.md b/storages/idb-storage/README.md new file mode 100644 index 000000000..afacfb2cb --- /dev/null +++ b/storages/idb-storage/README.md @@ -0,0 +1,6 @@ +## 🚴 IdbStorage - IndexedDB storage support for GlueSQL + +### 🔬 Test in Headless Browsers with `wasm-pack test` +``` +WASM_BINDGEN_TEST_TIMEOUT=60 wasm-pack test --headless --firefox --chrome +``` diff --git a/storages/idb-storage/src/convert.rs b/storages/idb-storage/src/convert.rs new file mode 100644 index 000000000..935338df6 --- /dev/null +++ b/storages/idb-storage/src/convert.rs @@ -0,0 +1,46 @@ +use { + super::ErrInto, + gloo_utils::format::JsValueSerdeExt, + gluesql_core::{ + ast::ColumnDef, + data::Value, + result::{Error, Result}, + store::DataRow, + }, + serde_json::Value as JsonValue, + std::collections::HashMap, + wasm_bindgen::JsValue, +}; + +pub fn convert(value: JsValue, column_defs: Option<&[ColumnDef]>) -> Result { + let value: JsonValue = value.into_serde().err_into()?; + + match (value, column_defs) { + (JsonValue::Array(json_array), Some(column_defs)) + if json_array.len() == column_defs.len() => + { + json_array + .into_iter() + .map(Value::try_from) + .zip(column_defs.iter()) + .map(|(value, ColumnDef { data_type, .. })| { + let value = value?; + + match value.get_type() { + Some(curr_type) if &curr_type != data_type => value.cast(data_type), + _ => Ok(value), + } + }) + .collect::>>() + .map(DataRow::Vec) + } + (JsonValue::Object(json_map), None) => json_map + .into_iter() + .map(|(key, value)| value.try_into().map(|value| (key, value))) + .collect::>>() + .map(DataRow::Map), + (value, _) => Err(Error::StorageMsg(format!( + "conflict - unsupported value stored: {value:?}" + ))), + } +} diff --git a/storages/idb-storage/src/error.rs b/storages/idb-storage/src/error.rs new file mode 100644 index 000000000..f6af1de41 --- /dev/null +++ b/storages/idb-storage/src/error.rs @@ -0,0 +1,14 @@ +use { + core::fmt::Display, + gluesql_core::result::{Error, Result}, +}; + +pub trait ErrInto { + fn err_into(self) -> Result; +} + +impl ErrInto for Result { + fn err_into(self) -> Result { + self.map_err(|e| Error::StorageMsg(e.to_string())) + } +} diff --git a/storages/idb-storage/src/lib.rs b/storages/idb-storage/src/lib.rs new file mode 100644 index 000000000..e210ba3f8 --- /dev/null +++ b/storages/idb-storage/src/lib.rs @@ -0,0 +1,434 @@ +#![cfg(target_arch = "wasm32")] +#![deny(clippy::str_to_string)] + +pub mod convert; +mod error; + +use { + async_trait::async_trait, + convert::convert, + error::ErrInto, + gloo_utils::format::JsValueSerdeExt, + gluesql_core::{ + data::{Key, Schema, Value}, + result::{Error, Result}, + store::{DataRow, RowIter, Store, StoreMut}, + }, + idb::{CursorDirection, Database, Factory, ObjectStoreParams, Query, TransactionMode}, + serde_json::Value as JsonValue, + std::{ + iter::empty, + sync::{Arc, Mutex}, + }, + wasm_bindgen::JsValue, + web_sys::console, +}; + +const SCHEMA_STORE: &str = "gluesql-schema"; +const DEFAULT_NAMESPACE: &str = "gluesql"; + +pub struct IdbStorage { + namespace: String, + factory: Factory, + database: Database, +} + +impl IdbStorage { + pub async fn new(namespace: Option) -> Result { + let factory = Factory::new().err_into()?; + let namespace = namespace.as_deref().unwrap_or(DEFAULT_NAMESPACE).to_owned(); + + let error = Arc::new(Mutex::new(None)); + let open_request = { + let error = Arc::clone(&error); + let mut open_request = factory.open(namespace.as_str(), None).err_into()?; + open_request.on_upgrade_needed(move |event| { + let database = match event.database().err_into() { + Ok(database) => database, + Err(e) => { + let mut error = match error.lock() { + Ok(error) => error, + Err(_) => { + let msg = JsValue::from_str("infallible - lock acquire failed"); + console::error_1(&msg); + return; + } + }; + + *error = Some(e); + return; + } + }; + + if let Err(e) = database + .create_object_store(SCHEMA_STORE, ObjectStoreParams::new()) + .err_into() + { + let mut error = match error.lock() { + Ok(error) => error, + Err(_) => { + let msg = JsValue::from_str("infallible - lock acquire failed"); + console::error_1(&msg); + return; + } + }; + + *error = Some(e); + return; + } + }); + + open_request + }; + + let database = open_request.await.err_into()?; + if let Some(e) = Arc::try_unwrap(error) + .map_err(|_| Error::StorageMsg("infallible - Arc::try_unwrap failed".to_owned()))? + .into_inner() + .err_into()? + { + return Err(e); + } + + Ok(Self { + namespace, + factory, + database, + }) + } + + pub async fn delete(&self) -> Result<()> { + self.factory.delete(&self.namespace).await.err_into() + } +} + +#[async_trait(?Send)] +impl Store for IdbStorage { + async fn fetch_all_schemas(&self) -> Result> { + let transaction = self + .database + .transaction(&[SCHEMA_STORE], TransactionMode::ReadOnly) + .err_into()?; + + let store = transaction.object_store(SCHEMA_STORE).err_into()?; + let schemas = store.get_all(None, None).await.err_into()?; + + transaction.commit().await.err_into()?; + schemas + .into_iter() + .map(|v| serde_wasm_bindgen::from_value(v).err_into()) + .collect::>>() + } + + async fn fetch_schema(&self, table_name: &str) -> Result> { + let transaction = self + .database + .transaction(&[SCHEMA_STORE], TransactionMode::ReadOnly) + .err_into()?; + + let store = transaction.object_store(SCHEMA_STORE).err_into()?; + let schema = store.get(JsValue::from_str(table_name)).await.err_into()?; + let schema = schema + .map(|v| serde_wasm_bindgen::from_value(v).err_into()) + .transpose()?; + + transaction.commit().await.err_into()?; + Ok(schema) + } + + async fn fetch_data(&self, table_name: &str, target: &Key) -> Result> { + let column_defs = self + .fetch_schema(table_name) + .await? + .and_then(|schema| schema.column_defs); + let transaction = self + .database + .transaction(&[table_name], TransactionMode::ReadOnly) + .err_into()?; + + let store = transaction.object_store(table_name).err_into()?; + + let key: Value = target.clone().into(); + let key: JsonValue = key.try_into()?; + let key = JsValue::from_serde(&key).err_into()?; + let row = store.get(key).await.err_into()?; + + transaction.commit().await.err_into()?; + + row.map(|row| convert(row, column_defs.as_deref())) + .transpose() + } + + async fn scan_data(&self, table_name: &str) -> Result { + let column_defs = self + .fetch_schema(table_name) + .await? + .and_then(|schema| schema.column_defs); + let transaction = self + .database + .transaction(&[table_name], TransactionMode::ReadOnly) + .err_into()?; + + let store = transaction.object_store(table_name).err_into()?; + let cursor = store + .open_cursor(None, Some(CursorDirection::Next)) + .await + .err_into()?; + + let mut cursor = match cursor { + Some(cursor) => cursor, + None => { + return Ok(Box::new(empty())); + } + }; + + let mut rows = Vec::new(); + let mut current_key = cursor.key().err_into()?; + let mut current_row = cursor.value().err_into()?; + + while !current_key.is_null() { + let key: JsonValue = current_key.into_serde().err_into()?; + let key: Key = Value::try_from(key)?.try_into()?; + + let row = convert(current_row, column_defs.as_deref())?; + + rows.push((key, row)); + + cursor.advance(1).await.err_into()?; + current_key = cursor.key().err_into()?; + current_row = cursor.value().err_into()?; + } + + transaction.commit().await.err_into()?; + + let rows = rows.into_iter().map(Ok); + Ok(Box::new(rows)) + } +} + +#[async_trait(?Send)] +impl StoreMut for IdbStorage { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { + let version = self.database.version().err_into()? + 1; + self.database.close(); + + let error = Arc::new(Mutex::new(None)); + let open_request = { + let error = Arc::clone(&error); + let table_name = schema.table_name.to_owned(); + let mut open_request = self + .factory + .open(self.namespace.as_str(), Some(version)) + .err_into()?; + + open_request.on_upgrade_needed(move |event| { + let database = match event.database().err_into() { + Ok(database) => database, + Err(e) => { + let mut error = match error.lock() { + Ok(error) => error, + Err(_) => { + let msg = JsValue::from_str("infallible - lock acquire failed"); + console::error_1(&msg); + return; + } + }; + + *error = Some(e); + return; + } + }; + + let mut params = ObjectStoreParams::new(); + params.auto_increment(true); + + if let Err(e) = database.create_object_store(&table_name, params).err_into() { + let mut error = match error.lock() { + Ok(error) => error, + Err(_) => { + let msg = JsValue::from_str("infallible - lock acquire failed"); + console::error_1(&msg); + return; + } + }; + + *error = Some(e); + } + }); + + open_request + }; + + self.database = open_request.await.err_into()?; + if let Some(e) = Arc::try_unwrap(error) + .map_err(|_| Error::StorageMsg("infallible - Arc::try_unwrap failed".to_owned()))? + .into_inner() + .err_into()? + { + return Err(e); + } + + let transaction = self + .database + .transaction(&[SCHEMA_STORE], TransactionMode::ReadWrite) + .err_into()?; + let store = transaction.object_store(SCHEMA_STORE).err_into()?; + + let key = JsValue::from_str(&schema.table_name); + let schema = JsValue::from_serde(&schema).err_into()?; + store.add(&schema, Some(&key)).await.err_into()?; + + transaction.commit().await.err_into() + } + + async fn delete_schema(&mut self, table_name: &str) -> Result<()> { + let version = self.database.version().err_into()? + 1; + self.database.close(); + + let error = Arc::new(Mutex::new(None)); + let open_request = { + let error = Arc::clone(&error); + let table_name = table_name.to_owned(); + let mut open_request = self + .factory + .open(self.namespace.as_str(), Some(version)) + .err_into()?; + + open_request.on_upgrade_needed(move |event| { + let database = match event.database().err_into() { + Ok(database) => database, + Err(e) => { + let mut error = match error.lock() { + Ok(error) => error, + Err(_) => { + let msg = JsValue::from_str("infallible - lock acquire failed"); + console::error_1(&msg); + return; + } + }; + + *error = Some(e); + return; + } + }; + + if !database + .store_names() + .iter() + .any(|name| name == &table_name) + { + return; + } + + if let Err(e) = database.delete_object_store(table_name.as_str()).err_into() { + let mut error = match error.lock() { + Ok(error) => error, + Err(_) => { + let msg = JsValue::from_str("infallible - lock acquire failed"); + console::error_1(&msg); + return; + } + }; + + *error = Some(e); + } + }); + + open_request + }; + + self.database = open_request.await.err_into()?; + if let Some(e) = Arc::try_unwrap(error) + .map_err(|_| Error::StorageMsg("infallible - Arc::try_unwrap failed".to_owned()))? + .into_inner() + .err_into()? + { + return Err(e); + } + + let transaction = self + .database + .transaction(&[SCHEMA_STORE], TransactionMode::ReadWrite) + .err_into()?; + let store = transaction.object_store(SCHEMA_STORE).err_into()?; + + let key = JsValue::from_str(table_name); + store.delete(Query::from(key)).await.err_into()?; + + transaction.commit().await.err_into() + } + + async fn append_data(&mut self, table_name: &str, new_rows: Vec) -> Result<()> { + let transaction = self + .database + .transaction(&[table_name], TransactionMode::ReadWrite) + .err_into()?; + let store = transaction.object_store(table_name).err_into()?; + + for data_row in new_rows { + let row = match data_row { + DataRow::Vec(values) => Value::List(values), + DataRow::Map(values) => Value::Map(values), + }; + + let row = JsonValue::try_from(row)?; + let row = JsValue::from_serde(&row).err_into()?; + + store.add(&row, None).await.err_into()?; + } + + transaction.commit().await.err_into() + } + + async fn insert_data(&mut self, table_name: &str, new_rows: Vec<(Key, DataRow)>) -> Result<()> { + let transaction = self + .database + .transaction(&[table_name], TransactionMode::ReadWrite) + .err_into()?; + let store = transaction.object_store(table_name).err_into()?; + + for (key, data_row) in new_rows { + let row = match data_row { + DataRow::Vec(values) => Value::List(values), + DataRow::Map(values) => Value::Map(values), + }; + + let row = JsonValue::try_from(row)?; + let row = JsValue::from_serde(&row).err_into()?; + + let key: JsonValue = Value::from(key).try_into()?; + let key = JsValue::from_serde(&key).err_into()?; + + store.put(&row, Some(&key)).await.err_into()?; + } + + transaction.commit().await.err_into() + } + + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { + let transaction = self + .database + .transaction(&[table_name], TransactionMode::ReadWrite) + .err_into()?; + let store = transaction.object_store(table_name).err_into()?; + + for key in keys { + let key: JsonValue = Value::from(key).try_into()?; + let key = JsValue::from_serde(&key).err_into()?; + let key = Query::from(key); + + store.delete(key).await.err_into()?; + } + + transaction.commit().await.err_into() + } +} + +#[cfg(feature = "alter-table")] +impl gluesql_core::store::AlterTable for IdbStorage {} +#[cfg(feature = "index")] +impl gluesql_core::store::Index for IdbStorage {} +#[cfg(feature = "index")] +impl gluesql_core::store::IndexMut for IdbStorage {} +#[cfg(feature = "transaction")] +impl gluesql_core::store::Transaction for IdbStorage {} diff --git a/storages/idb-storage/tests/convert.rs b/storages/idb-storage/tests/convert.rs new file mode 100644 index 000000000..c2d28507d --- /dev/null +++ b/storages/idb-storage/tests/convert.rs @@ -0,0 +1,74 @@ +#![cfg(target_arch = "wasm32")] + +use { + gloo_utils::format::JsValueSerdeExt, + gluesql_core::{ + ast::{ColumnDef, DataType}, + data::Value, + store::DataRow, + }, + gluesql_idb_storage::convert::convert, + serde_json::json, + wasm_bindgen::JsValue, + wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}, +}; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn convert_schema() { + let actual_data = json!([100, "hello", true]); + let actual_data = JsValue::from_serde(&actual_data).unwrap(); + let actual_defs = vec![ + ColumnDef { + name: "id".to_owned(), + data_type: DataType::Int8, + nullable: false, + default: None, + unique: None, + }, + ColumnDef { + name: "name".to_owned(), + data_type: DataType::Text, + nullable: false, + default: None, + unique: None, + }, + ColumnDef { + name: "flag".to_owned(), + data_type: DataType::Boolean, + nullable: true, + default: None, + unique: None, + }, + ]; + let expected = DataRow::Vec(vec![ + Value::I8(100), + Value::Str("hello".to_owned()), + Value::Bool(true), + ]); + assert_eq!( + convert(actual_data, Some(actual_defs.as_slice())), + Ok(expected) + ); +} + +#[wasm_bindgen_test] +fn convert_schemaless() { + let actual = json!({ + "id": 100, + "name": "hello", + "flag": true + }); + let actual = JsValue::from_serde(&actual).unwrap(); + let expected = DataRow::Map( + [ + ("id".to_owned(), Value::I64(100)), + ("name".to_owned(), Value::Str("hello".to_owned())), + ("flag".to_owned(), Value::Bool(true)), + ] + .into_iter() + .collect(), + ); + assert_eq!(convert(actual, None), Ok(expected)); +} diff --git a/storages/idb-storage/tests/idb_storage.rs b/storages/idb-storage/tests/idb_storage.rs new file mode 100644 index 000000000..6c7ed7a5a --- /dev/null +++ b/storages/idb-storage/tests/idb_storage.rs @@ -0,0 +1,31 @@ +#![cfg(target_arch = "wasm32")] + +use { + async_trait::async_trait, + gluesql_core::prelude::Glue, + gluesql_idb_storage::IdbStorage, + test_suite::*, + wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}, +}; + +wasm_bindgen_test_configure!(run_in_browser); + +struct IdbStorageTester { + glue: Glue, +} + +#[async_trait(?Send)] +impl Tester for IdbStorageTester { + async fn new(namespace: &str) -> Self { + let storage = IdbStorage::new(Some(namespace.to_owned())).await.unwrap(); + let glue = Glue::new(storage); + + Self { glue } + } + + fn get_glue(&mut self) -> &mut Glue { + &mut self.glue + } +} + +generate_store_tests!(wasm_bindgen_test, IdbStorageTester); diff --git a/storages/memory-storage/Cargo.toml b/storages/memory-storage/Cargo.toml index ab80e2169..88fe9cb56 100644 --- a/storages/memory-storage/Cargo.toml +++ b/storages/memory-storage/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "gluesql_memory_storage" version = "0.13.0" -edition = "2021" authors = ["Taehoon Moon "] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] gluesql-core = { path = "../../core", version = "0.13.0" } diff --git a/storages/memory-storage/src/alter_table.rs b/storages/memory-storage/src/alter_table.rs index e5248a11f..7cca3170e 100644 --- a/storages/memory-storage/src/alter_table.rs +++ b/storages/memory-storage/src/alter_table.rs @@ -6,13 +6,14 @@ use { gluesql_core::{ ast::ColumnDef, data::Value, - result::{Error, MutResult, Result, TrySelf}, + result::{Error, Result}, store::{AlterTable, AlterTableError, DataRow}, }, }; -impl MemoryStorage { - pub fn rename_schema(&mut self, table_name: &str, new_table_name: &str) -> Result<()> { +#[async_trait(?Send)] +impl AlterTable for MemoryStorage { + async fn rename_schema(&mut self, table_name: &str, new_table_name: &str) -> Result<()> { let mut item = self .items .remove(table_name) @@ -24,7 +25,7 @@ impl MemoryStorage { Ok(()) } - pub fn rename_column( + async fn rename_column( &mut self, table_name: &str, old_column_name: &str, @@ -58,7 +59,7 @@ impl MemoryStorage { Ok(()) } - pub fn add_column(&mut self, table_name: &str, column_def: &ColumnDef) -> Result<()> { + async fn add_column(&mut self, table_name: &str, column_def: &ColumnDef) -> Result<()> { let item = self .items .get_mut(table_name) @@ -116,7 +117,7 @@ impl MemoryStorage { Ok(()) } - pub fn drop_column( + async fn drop_column( &mut self, table_name: &str, column_name: &str, @@ -167,42 +168,3 @@ impl MemoryStorage { Ok(()) } } - -#[async_trait(?Send)] -impl AlterTable for MemoryStorage { - async fn rename_schema(self, table_name: &str, new_table_name: &str) -> MutResult { - let mut storage = self; - - MemoryStorage::rename_schema(&mut storage, table_name, new_table_name).try_self(storage) - } - - async fn rename_column( - self, - table_name: &str, - old_column_name: &str, - new_column_name: &str, - ) -> MutResult { - let mut storage = self; - - MemoryStorage::rename_column(&mut storage, table_name, old_column_name, new_column_name) - .try_self(storage) - } - - async fn add_column(self, table_name: &str, column_def: &ColumnDef) -> MutResult { - let mut storage = self; - - MemoryStorage::add_column(&mut storage, table_name, column_def).try_self(storage) - } - - async fn drop_column( - self, - table_name: &str, - column_name: &str, - if_exists: bool, - ) -> MutResult { - let mut storage = self; - - MemoryStorage::drop_column(&mut storage, table_name, column_name, if_exists) - .try_self(storage) - } -} diff --git a/storages/memory-storage/src/index.rs b/storages/memory-storage/src/index.rs index 6269597da..217c412c2 100644 --- a/storages/memory-storage/src/index.rs +++ b/storages/memory-storage/src/index.rs @@ -6,7 +6,7 @@ use { gluesql_core::{ ast::{IndexOperator, OrderByExpr}, data::Value, - result::{Error, MutResult, Result}, + result::{Error, Result}, store::{Index, IndexMut, RowIter}, }, }; @@ -29,21 +29,19 @@ impl Index for MemoryStorage { #[async_trait(?Send)] impl IndexMut for MemoryStorage { async fn create_index( - self, + &mut self, _table_name: &str, _index_name: &str, _column: &OrderByExpr, - ) -> MutResult { - Err(( - self, - Error::StorageMsg("[MemoryStorage] index is not supported".to_owned()), + ) -> Result<()> { + Err(Error::StorageMsg( + "[MemoryStorage] index is not supported".to_owned(), )) } - async fn drop_index(self, _table_name: &str, _index_name: &str) -> MutResult { - Err(( - self, - Error::StorageMsg("[MemoryStorage] index is not supported".to_owned()), + async fn drop_index(&mut self, _table_name: &str, _index_name: &str) -> Result<()> { + Err(Error::StorageMsg( + "[MemoryStorage] index is not supported".to_owned(), )) } } diff --git a/storages/memory-storage/src/lib.rs b/storages/memory-storage/src/lib.rs index 62143ed4d..c18b5fc75 100644 --- a/storages/memory-storage/src/lib.rs +++ b/storages/memory-storage/src/lib.rs @@ -8,7 +8,7 @@ use { async_trait::async_trait, gluesql_core::{ data::{Key, Schema}, - result::{MutResult, Result}, + result::Result, store::{DataRow, RowIter, Store, StoreMut}, }, indexmap::IndexMap, @@ -66,8 +66,9 @@ impl Store for MemoryStorage { } } -impl MemoryStorage { - pub fn insert_schema(&mut self, schema: &Schema) { +#[async_trait(?Send)] +impl StoreMut for MemoryStorage { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { let table_name = schema.table_name.clone(); let item = Item { schema: schema.clone(), @@ -75,13 +76,15 @@ impl MemoryStorage { }; self.items.insert(table_name, item); + Ok(()) } - pub fn delete_schema(&mut self, table_name: &str) { + async fn delete_schema(&mut self, table_name: &str) -> Result<()> { self.items.remove(table_name); + Ok(()) } - pub fn append_data(&mut self, table_name: &str, rows: Vec) { + async fn append_data(&mut self, table_name: &str, rows: Vec) -> Result<()> { if let Some(item) = self.items.get_mut(table_name) { for row in rows { self.id_counter += 1; @@ -89,64 +92,27 @@ impl MemoryStorage { item.rows.insert(Key::I64(self.id_counter), row); } } + + Ok(()) } - pub fn insert_data(&mut self, table_name: &str, rows: Vec<(Key, DataRow)>) { + async fn insert_data(&mut self, table_name: &str, rows: Vec<(Key, DataRow)>) -> Result<()> { if let Some(item) = self.items.get_mut(table_name) { for (key, row) in rows { item.rows.insert(key, row); } } + + Ok(()) } - pub fn delete_data(&mut self, table_name: &str, keys: Vec) { + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { if let Some(item) = self.items.get_mut(table_name) { for key in keys { item.rows.remove(&key); } } - } -} - -#[async_trait(?Send)] -impl StoreMut for MemoryStorage { - async fn insert_schema(self, schema: &Schema) -> MutResult { - let mut storage = self; - - MemoryStorage::insert_schema(&mut storage, schema); - - Ok((storage, ())) - } - - async fn delete_schema(self, table_name: &str) -> MutResult { - let mut storage = self; - - MemoryStorage::delete_schema(&mut storage, table_name); - - Ok((storage, ())) - } - - async fn append_data(self, table_name: &str, rows: Vec) -> MutResult { - let mut storage = self; - - MemoryStorage::append_data(&mut storage, table_name, rows); - - Ok((storage, ())) - } - - async fn insert_data(self, table_name: &str, rows: Vec<(Key, DataRow)>) -> MutResult { - let mut storage = self; - - MemoryStorage::insert_data(&mut storage, table_name, rows); - - Ok((storage, ())) - } - - async fn delete_data(self, table_name: &str, keys: Vec) -> MutResult { - let mut storage = self; - - MemoryStorage::delete_data(&mut storage, table_name, keys); - Ok((storage, ())) + Ok(()) } } diff --git a/storages/memory-storage/src/transaction.rs b/storages/memory-storage/src/transaction.rs index 618aa9a12..7f6ba7b4d 100644 --- a/storages/memory-storage/src/transaction.rs +++ b/storages/memory-storage/src/transaction.rs @@ -4,35 +4,28 @@ use { super::MemoryStorage, async_trait::async_trait, gluesql_core::{ - result::{Error, MutResult}, + result::{Error, Result}, store::Transaction, }, }; #[async_trait(?Send)] impl Transaction for MemoryStorage { - async fn begin(self, autocommit: bool) -> MutResult { + async fn begin(&mut self, autocommit: bool) -> Result { if autocommit { - return Ok((self, false)); + return Ok(false); } - Err(( - self, - Error::StorageMsg("[MemoryStorage] transaction is not supported".to_owned()), + Err(Error::StorageMsg( + "[MemoryStorage] transaction is not supported".to_owned(), )) } - async fn rollback(self) -> MutResult { - Err(( - self, - Error::StorageMsg("[MemoryStorage] transaction is not supported".to_owned()), - )) + async fn rollback(&mut self) -> Result<()> { + Ok(()) } - async fn commit(self) -> MutResult { - Err(( - self, - Error::StorageMsg("[MemoryStorage] transaction is not supported".to_owned()), - )) + async fn commit(&mut self) -> Result<()> { + Ok(()) } } diff --git a/storages/memory-storage/tests/memory_storage.rs b/storages/memory-storage/tests/memory_storage.rs index 80c129619..fc1cba9be 100644 --- a/storages/memory-storage/tests/memory_storage.rs +++ b/storages/memory-storage/tests/memory_storage.rs @@ -86,13 +86,16 @@ fn memory_storage_index() { #[cfg(feature = "transaction")] #[test] fn memory_storage_transaction() { - use gluesql_core::{prelude::Glue, result::Error}; + use gluesql_core::{ + prelude::{Glue, Payload}, + result::Error, + }; let storage = MemoryStorage::default(); let mut glue = Glue::new(storage); exec!(glue "CREATE TABLE TxTest (id INTEGER);"); test!(glue "BEGIN", Err(Error::StorageMsg("[MemoryStorage] transaction is not supported".to_owned()))); - test!(glue "COMMIT", Err(Error::StorageMsg("[MemoryStorage] transaction is not supported".to_owned()))); - test!(glue "ROLLBACK", Err(Error::StorageMsg("[MemoryStorage] transaction is not supported".to_owned()))); + test!(glue "COMMIT", Ok(vec![Payload::Commit])); + test!(glue "ROLLBACK", Ok(vec![Payload::Rollback])); } diff --git a/storages/shared-memory-storage/Cargo.toml b/storages/shared-memory-storage/Cargo.toml index b6f8db6d2..5943123d7 100644 --- a/storages/shared-memory-storage/Cargo.toml +++ b/storages/shared-memory-storage/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "gluesql-shared-memory-storage" version = "0.13.0" -edition = "2021" authors = [ "Jiseok CHOI ", "Taehoon Moon ", ] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] memory-storage = { package = "gluesql_memory_storage", version = "0.13.0", path = "../memory-storage" } diff --git a/storages/shared-memory-storage/src/alter_table.rs b/storages/shared-memory-storage/src/alter_table.rs index 865d4f09e..5b0ec0c95 100644 --- a/storages/shared-memory-storage/src/alter_table.rs +++ b/storages/shared-memory-storage/src/alter_table.rs @@ -1,72 +1,51 @@ use { super::SharedMemoryStorage, async_trait::async_trait, - gluesql_core::{ast::ColumnDef, result::MutResult, store::AlterTable}, - memory_storage::MemoryStorage, + gluesql_core::{ast::ColumnDef, result::Result, store::AlterTable}, std::sync::Arc, }; #[async_trait(?Send)] impl AlterTable for SharedMemoryStorage { - async fn rename_schema(self, table_name: &str, new_table_name: &str) -> MutResult { + async fn rename_schema(&mut self, table_name: &str, new_table_name: &str) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - if let Err(error) = MemoryStorage::rename_schema(&mut database, table_name, new_table_name) - { - return Err((self, error)); - } - - Ok((self, ())) + database.rename_schema(table_name, new_table_name).await } async fn rename_column( - self, + &mut self, table_name: &str, old_column_name: &str, new_column_name: &str, - ) -> MutResult { + ) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - if let Err(error) = MemoryStorage::rename_column( - &mut database, - table_name, - old_column_name, - new_column_name, - ) { - return Err((self, error)); - } - - Ok((self, ())) + database + .rename_column(table_name, old_column_name, new_column_name) + .await } - async fn add_column(self, table_name: &str, column_def: &ColumnDef) -> MutResult { + async fn add_column(&mut self, table_name: &str, column_def: &ColumnDef) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - if let Err(error) = MemoryStorage::add_column(&mut database, table_name, column_def) { - return Err((self, error)); - } - - Ok((self, ())) + database.add_column(table_name, column_def).await } async fn drop_column( - self, + &mut self, table_name: &str, column_name: &str, if_exists: bool, - ) -> MutResult { + ) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - if let Err(error) = - MemoryStorage::drop_column(&mut database, table_name, column_name, if_exists) - { - return Err((self, error)); - } - - Ok((self, ())) + database + .drop_column(table_name, column_name, if_exists) + .await } } diff --git a/storages/shared-memory-storage/src/index.rs b/storages/shared-memory-storage/src/index.rs index 718fd39c2..bfb2daee4 100644 --- a/storages/shared-memory-storage/src/index.rs +++ b/storages/shared-memory-storage/src/index.rs @@ -4,7 +4,7 @@ use { gluesql_core::{ ast::{IndexOperator, OrderByExpr}, data::Value, - result::{Error, MutResult, Result}, + result::{Error, Result}, store::{Index, IndexMut, RowIter}, }, }; @@ -27,21 +27,19 @@ impl Index for SharedMemoryStorage { #[async_trait(?Send)] impl IndexMut for SharedMemoryStorage { async fn create_index( - self, + &mut self, _table_name: &str, _index_name: &str, _column: &OrderByExpr, - ) -> MutResult { - Err(( - self, - Error::StorageMsg("[Shared MemoryStorage] index is not supported".to_owned()), + ) -> Result<()> { + Err(Error::StorageMsg( + "[Shared MemoryStorage] index is not supported".to_owned(), )) } - async fn drop_index(self, _table_name: &str, _index_name: &str) -> MutResult { - Err(( - self, - Error::StorageMsg("[Shared MemoryStorage] index is not supported".to_owned()), + async fn drop_index(&mut self, _table_name: &str, _index_name: &str) -> Result<()> { + Err(Error::StorageMsg( + "[Shared MemoryStorage] index is not supported".to_owned(), )) } } diff --git a/storages/shared-memory-storage/src/lib.rs b/storages/shared-memory-storage/src/lib.rs index 0eaa83522..f1e1d05e0 100644 --- a/storages/shared-memory-storage/src/lib.rs +++ b/storages/shared-memory-storage/src/lib.rs @@ -8,7 +8,7 @@ use { async_trait::async_trait, gluesql_core::{ data::{Key, Schema}, - result::{MutResult, Result}, + result::Result, store::{DataRow, RowIter, Store, StoreMut}, }, memory_storage::MemoryStorage, @@ -75,48 +75,38 @@ impl Store for SharedMemoryStorage { #[async_trait(?Send)] impl StoreMut for SharedMemoryStorage { - async fn insert_schema(self, schema: &Schema) -> MutResult { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - MemoryStorage::insert_schema(&mut database, schema); - - Ok((self, ())) + database.insert_schema(schema).await } - async fn delete_schema(self, table_name: &str) -> MutResult { + async fn delete_schema(&mut self, table_name: &str) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - MemoryStorage::delete_schema(&mut database, table_name); - - Ok((self, ())) + database.delete_schema(table_name).await } - async fn append_data(self, table_name: &str, rows: Vec) -> MutResult { + async fn append_data(&mut self, table_name: &str, rows: Vec) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - MemoryStorage::append_data(&mut database, table_name, rows); - - Ok((self, ())) + database.append_data(table_name, rows).await } - async fn insert_data(self, table_name: &str, rows: Vec<(Key, DataRow)>) -> MutResult { + async fn insert_data(&mut self, table_name: &str, rows: Vec<(Key, DataRow)>) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - MemoryStorage::insert_data(&mut database, table_name, rows); - - Ok((self, ())) + database.insert_data(table_name, rows).await } - async fn delete_data(self, table_name: &str, keys: Vec) -> MutResult { + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { let database = Arc::clone(&self.database); let mut database = database.write().await; - MemoryStorage::delete_data(&mut database, table_name, keys); - - Ok((self, ())) + database.delete_data(table_name, keys).await } } diff --git a/storages/shared-memory-storage/src/transaction.rs b/storages/shared-memory-storage/src/transaction.rs index 0be7400ff..a318b8da1 100644 --- a/storages/shared-memory-storage/src/transaction.rs +++ b/storages/shared-memory-storage/src/transaction.rs @@ -2,35 +2,32 @@ use { super::SharedMemoryStorage, async_trait::async_trait, gluesql_core::{ - result::{Error, MutResult}, + result::{Error, Result}, store::Transaction, }, }; #[async_trait(?Send)] impl Transaction for SharedMemoryStorage { - async fn begin(self, autocommit: bool) -> MutResult { + async fn begin(&mut self, autocommit: bool) -> Result { if autocommit { - return Ok((self, false)); + return Ok(false); } - Err(( - self, - Error::StorageMsg("[Shared MemoryStorage] transaction is not supported".to_owned()), + Err(Error::StorageMsg( + "[Shared MemoryStorage] transaction is not supported".to_owned(), )) } - async fn rollback(self) -> MutResult { - Err(( - self, - Error::StorageMsg("[Shared MemoryStorage] transaction is not supported".to_owned()), + async fn rollback(&mut self) -> Result<()> { + Err(Error::StorageMsg( + "[Shared MemoryStorage] transaction is not supported".to_owned(), )) } - async fn commit(self) -> MutResult { - Err(( - self, - Error::StorageMsg("[Shared MemoryStorage] transaction is not supported".to_owned()), + async fn commit(&mut self) -> Result<()> { + Err(Error::StorageMsg( + "[Shared MemoryStorage] transaction is not supported".to_owned(), )) } } diff --git a/storages/sled-storage/Cargo.toml b/storages/sled-storage/Cargo.toml index 35293e86a..5cea6aa24 100644 --- a/storages/sled-storage/Cargo.toml +++ b/storages/sled-storage/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "gluesql_sled_storage" version = "0.13.0" -edition = "2021" authors = ["Taehoon Moon "] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] gluesql-core = { path = "../../core", version = "0.13.0", features = [ diff --git a/storages/sled-storage/src/alter_table.rs b/storages/sled-storage/src/alter_table.rs index 0105e2461..b22ff70e0 100644 --- a/storages/sled-storage/src/alter_table.rs +++ b/storages/sled-storage/src/alter_table.rs @@ -11,7 +11,7 @@ use { ast::ColumnDef, data::{schema::Schema, Value}, executor::evaluate_stateless, - result::{Error, MutResult, Result, TrySelf}, + result::{Error, Result}, store::{AlterTable, AlterTableError, DataRow}, }, sled::transaction::ConflictableTransactionError, @@ -21,14 +21,13 @@ use { #[async_trait(?Send)] impl AlterTable for SledStorage { - async fn rename_schema(self, table_name: &str, new_table_name: &str) -> MutResult { + async fn rename_schema(&mut self, table_name: &str, new_table_name: &str) -> Result<()> { let prefix = format!("data/{}/", table_name); let items = self .tree .scan_prefix(prefix.as_bytes()) .map(|item| item.map_err(err_into)) - .collect::>>(); - let (self, items) = items.try_self(self)?; + .collect::>>()?; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -50,6 +49,7 @@ impl AlterTable for SledStorage { let Schema { column_defs, indexes, + engine, created, .. } = old_schema @@ -60,6 +60,7 @@ impl AlterTable for SledStorage { table_name: new_table_name.to_owned(), column_defs, indexes, + engine, created, }; @@ -127,18 +128,19 @@ impl AlterTable for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| { - storage.rename_schema(table_name, new_table_name) - }) - .await + if self.check_retry(tx_result)? { + self.rename_schema(table_name, new_table_name).await?; + } + + Ok(()) } async fn rename_column( - self, + &mut self, table_name: &str, old_column_name: &str, new_column_name: &str, - ) -> MutResult { + ) -> Result<()> { let state = &self.state; let tx_timeout = self.tx_timeout; let tx_result = self.tree.transaction(move |tree| { @@ -157,6 +159,7 @@ impl AlterTable for SledStorage { let Schema { column_defs, indexes, + engine, created, .. } = snapshot @@ -205,6 +208,7 @@ impl AlterTable for SledStorage { table_name: table_name.to_owned(), column_defs: Some(column_defs), indexes, + engine, created, }; let (snapshot, _) = snapshot.update(txid, schema); @@ -222,20 +226,21 @@ impl AlterTable for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| { - storage.rename_column(table_name, old_column_name, new_column_name) - }) - .await + if self.check_retry(tx_result)? { + self.rename_column(table_name, old_column_name, new_column_name) + .await?; + } + + Ok(()) } - async fn add_column(self, table_name: &str, column_def: &ColumnDef) -> MutResult { + async fn add_column(&mut self, table_name: &str, column_def: &ColumnDef) -> Result<()> { let prefix = format!("data/{}/", table_name); let items = self .tree .scan_prefix(prefix.as_bytes()) .map(|item| item.map_err(err_into)) - .collect::>>(); - let (self, items) = items.try_self(self)?; + .collect::>>()?; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -256,6 +261,7 @@ impl AlterTable for SledStorage { table_name, column_defs, indexes, + engine, created, .. } = schema_snapshot @@ -353,6 +359,7 @@ impl AlterTable for SledStorage { table_name, column_defs: Some(column_defs), indexes, + engine, created, }; let (schema_snapshot, _) = schema_snapshot.update(txid, schema); @@ -369,25 +376,25 @@ impl AlterTable for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| { - storage.add_column(table_name, column_def) - }) - .await + if self.check_retry(tx_result)? { + self.add_column(table_name, column_def).await?; + } + + Ok(()) } async fn drop_column( - self, + &mut self, table_name: &str, column_name: &str, if_exists: bool, - ) -> MutResult { + ) -> Result<()> { let prefix = format!("data/{}/", table_name); let items = self .tree .scan_prefix(prefix.as_bytes()) .map(|item| item.map_err(err_into)) - .collect::>>(); - let (self, items) = items.try_self(self)?; + .collect::>>()?; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -408,6 +415,7 @@ impl AlterTable for SledStorage { table_name, column_defs, indexes, + engine, created, .. } = schema_snapshot @@ -491,6 +499,7 @@ impl AlterTable for SledStorage { table_name, column_defs: Some(column_defs), indexes, + engine, created, }; let (schema_snapshot, _) = schema_snapshot.update(txid, schema); @@ -506,9 +515,10 @@ impl AlterTable for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| { - storage.drop_column(table_name, column_name, if_exists) - }) - .await + if self.check_retry(tx_result)? { + self.drop_column(table_name, column_name, if_exists).await?; + } + + Ok(()) } } diff --git a/storages/sled-storage/src/index_mut.rs b/storages/sled-storage/src/index_mut.rs index 5f87e7ac1..5c7e20544 100644 --- a/storages/sled-storage/src/index_mut.rs +++ b/storages/sled-storage/src/index_mut.rs @@ -12,7 +12,7 @@ use { ast::OrderByExpr, chrono::Utc, data::{Schema, SchemaIndex, SchemaIndexOrd}, - result::{Error, MutResult, Result, TrySelf}, + result::{Error, Result}, store::{IndexError, IndexMut, Store}, }, sled::transaction::{ @@ -39,13 +39,15 @@ fn fetch_schema( #[async_trait(?Send)] impl IndexMut for SledStorage { async fn create_index( - self, + &mut self, table_name: &str, index_name: &str, column: &OrderByExpr, - ) -> MutResult { - let (self, rows) = self.scan_data(table_name).await.try_self(self)?; - let (self, rows) = rows.collect::>>().try_self(self)?; + ) -> Result<()> { + let rows = self + .scan_data(table_name) + .await? + .collect::>>()?; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -68,6 +70,7 @@ impl IndexMut for SledStorage { let Schema { column_defs, indexes, + engine, created, .. } = schema @@ -95,6 +98,7 @@ impl IndexMut for SledStorage { table_name: table_name.to_owned(), column_defs, indexes, + engine, created, }; @@ -119,15 +123,18 @@ impl IndexMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| { - storage.create_index(table_name, index_name, column) - }) - .await + if self.check_retry(tx_result)? { + self.create_index(table_name, index_name, column).await?; + } + + Ok(()) } - async fn drop_index(self, table_name: &str, index_name: &str) -> MutResult { - let (self, rows) = self.scan_data(table_name).await.try_self(self)?; - let (self, rows) = rows.collect::>>().try_self(self)?; + async fn drop_index(&mut self, table_name: &str, index_name: &str) -> Result<()> { + let rows = self + .scan_data(table_name) + .await? + .collect::>>()?; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -148,6 +155,7 @@ impl IndexMut for SledStorage { let Schema { column_defs, indexes, + engine, created, .. } = schema @@ -170,6 +178,7 @@ impl IndexMut for SledStorage { table_name: table_name.to_owned(), column_defs, indexes, + engine, created, }; @@ -194,9 +203,10 @@ impl IndexMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| { - storage.drop_index(table_name, index_name) - }) - .await + if self.check_retry(tx_result)? { + self.drop_index(table_name, index_name).await?; + } + + Ok(()) } } diff --git a/storages/sled-storage/src/lib.rs b/storages/sled-storage/src/lib.rs index b25babaec..986b3d1f6 100644 --- a/storages/sled-storage/src/lib.rs +++ b/storages/sled-storage/src/lib.rs @@ -97,15 +97,6 @@ impl SledStorage { Ok(()) } - - fn update_state(self, state: State) -> Self { - Self { - tree: self.tree, - id_offset: self.id_offset, - state, - tx_timeout: self.tx_timeout, - } - } } impl TryFrom for SledStorage { diff --git a/storages/sled-storage/src/store_mut.rs b/storages/sled-storage/src/store_mut.rs index b04587cd5..c86a01c63 100644 --- a/storages/sled-storage/src/store_mut.rs +++ b/storages/sled-storage/src/store_mut.rs @@ -5,12 +5,12 @@ use { key, lock::{self, LockAcquired}, transaction::TxPayload, - SledStorage, Snapshot, + tx_err_into, SledStorage, Snapshot, }, async_trait::async_trait, gluesql_core::{ data::{Key, Schema}, - result::{MutResult, Result}, + result::Result, store::{DataRow, IndexError, StoreMut}, }, sled::transaction::ConflictableTransactionError, @@ -18,7 +18,7 @@ use { #[async_trait(?Send)] impl StoreMut for SledStorage { - async fn insert_schema(self, schema: &Schema) -> MutResult { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { let state = &self.state; let tx_timeout = self.tx_timeout; @@ -55,23 +55,25 @@ impl StoreMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| storage.insert_schema(schema)) - .await + if let TxPayload::RollbackAndRetry(lock_txid) = tx_result.map_err(tx_err_into)? { + self.rollback_txid(lock_txid)?; + self.tree + .transaction(move |tree| lock::release(tree, lock_txid)) + .map_err(tx_err_into)?; + + self.insert_schema(schema).await?; + } + + Ok(()) } - async fn delete_schema(self, table_name: &str) -> MutResult { + async fn delete_schema(&mut self, table_name: &str) -> Result<()> { let prefix = format!("data/{}/", table_name); let items = self .tree .scan_prefix(prefix.as_bytes()) .map(|item| item.map_err(err_into)) - .collect::>>(); - let items = match items { - Ok(items) => items, - Err(e) => { - return Err((self, e)); - } - }; + .collect::>>()?; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -138,11 +140,19 @@ impl StoreMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| storage.delete_schema(table_name)) - .await + if let TxPayload::RollbackAndRetry(lock_txid) = tx_result.map_err(tx_err_into)? { + self.rollback_txid(lock_txid)?; + self.tree + .transaction(move |tree| lock::release(tree, lock_txid)) + .map_err(tx_err_into)?; + + self.delete_schema(table_name).await?; + } + + Ok(()) } - async fn append_data(self, table_name: &str, rows: Vec) -> MutResult { + async fn append_data(&mut self, table_name: &str, rows: Vec) -> Result<()> { let id_offset = self.id_offset; let state = &self.state; let tx_timeout = self.tx_timeout; @@ -182,11 +192,19 @@ impl StoreMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| storage.append_data(table_name, rows)) - .await + if let TxPayload::RollbackAndRetry(lock_txid) = tx_result.map_err(tx_err_into)? { + self.rollback_txid(lock_txid)?; + self.tree + .transaction(move |tree| lock::release(tree, lock_txid)) + .map_err(tx_err_into)?; + + self.append_data(table_name, rows).await?; + } + + Ok(()) } - async fn insert_data(self, table_name: &str, rows: Vec<(Key, DataRow)>) -> MutResult { + async fn insert_data(&mut self, table_name: &str, rows: Vec<(Key, DataRow)>) -> Result<()> { let state = &self.state; let tx_timeout = self.tx_timeout; let tx_rows = &rows; @@ -244,11 +262,19 @@ impl StoreMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| storage.insert_data(table_name, rows)) - .await + if let TxPayload::RollbackAndRetry(lock_txid) = tx_result.map_err(tx_err_into)? { + self.rollback_txid(lock_txid)?; + self.tree + .transaction(move |tree| lock::release(tree, lock_txid)) + .map_err(tx_err_into)?; + + self.insert_data(table_name, rows).await?; + } + + Ok(()) } - async fn delete_data(self, table_name: &str, keys: Vec) -> MutResult { + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { let state = &self.state; let tx_timeout = self.tx_timeout; let tx_keys = &keys; @@ -298,7 +324,15 @@ impl StoreMut for SledStorage { Ok(TxPayload::Success) }); - self.check_and_retry(tx_result, |storage| storage.delete_data(table_name, keys)) - .await + if let TxPayload::RollbackAndRetry(lock_txid) = tx_result.map_err(tx_err_into)? { + self.rollback_txid(lock_txid)?; + self.tree + .transaction(move |tree| lock::release(tree, lock_txid)) + .map_err(tx_err_into)?; + + self.delete_data(table_name, keys).await?; + } + + Ok(()) } } diff --git a/storages/sled-storage/src/transaction.rs b/storages/sled-storage/src/transaction.rs index daf0e23a9..b697bcee2 100644 --- a/storages/sled-storage/src/transaction.rs +++ b/storages/sled-storage/src/transaction.rs @@ -1,15 +1,13 @@ use { super::{ - err_into, - error::StorageError, - key, + err_into, key, lock::{self, Lock}, tx_err_into, SledStorage, Snapshot, State, }, async_trait::async_trait, gluesql_core::{ data::Schema, - result::{Error, MutResult, Result}, + result::{Error, Result}, store::{DataRow, Transaction}, }, serde::{de::DeserializeOwned, Serialize}, @@ -23,29 +21,6 @@ use { std::result::Result as StdResult, }; -macro_rules! transaction { - ($self: expr, $expr: expr) => {{ - let result = $self.tree.transaction($expr).map_err(|e| match e { - TransactionError::Abort(e) => e, - TransactionError::Storage(e) => StorageError::Sled(e).into(), - }); - - match result { - Ok(v) => { - let storage = Self { - tree: $self.tree, - id_offset: $self.id_offset, - state: State::Idle, - tx_timeout: $self.tx_timeout, - }; - - Ok((storage, v)) - } - Err(e) => Err(($self, e)), - } - }}; -} - pub enum TxPayload { Success, RollbackAndRetry(u64), @@ -53,40 +28,31 @@ pub enum TxPayload { #[async_trait(?Send)] impl Transaction for SledStorage { - async fn begin(self, autocommit: bool) -> MutResult { + async fn begin(&mut self, autocommit: bool) -> Result { match (&self.state, autocommit) { - (State::Transaction { .. }, false) => Err(( - self, - Error::StorageMsg("nested transaction is not supported".to_owned()), + (State::Transaction { .. }, false) => Err(Error::StorageMsg( + "nested transaction is not supported".to_owned(), )), - (State::Transaction { autocommit, .. }, true) => { - let autocommit = *autocommit; + (State::Transaction { autocommit, .. }, true) => Ok(*autocommit), + (State::Idle, _) => { + let (txid, created_at) = lock::register(&self.tree, self.id_offset)?; + + self.state = State::Transaction { + txid, + created_at, + autocommit, + }; - Ok((self, autocommit)) + Ok(autocommit) } - (State::Idle, _) => match lock::register(&self.tree, self.id_offset) { - Ok((txid, created_at)) => { - let state = State::Transaction { - txid, - created_at, - autocommit, - }; - - Ok((self.update_state(state), autocommit)) - } - Err(e) => Err((self, e)), - }, } } - async fn rollback(self) -> MutResult { + async fn rollback(&mut self) -> Result<()> { let txid = match self.state { State::Transaction { txid, .. } => txid, State::Idle => { - return Err(( - self, - Error::StorageMsg("no transaction to rollback".to_owned()), - )); + return Err(Error::StorageMsg("no transaction to rollback".to_owned())); } }; @@ -107,53 +73,49 @@ impl Transaction for SledStorage { } }; - match rollback() { - Ok(lock_txid) => transaction!(self, move |tree| { + let lock_txid = rollback()?; + + self.tree + .transaction(move |tree| { lock_txid .map(|lock_txid| lock::release(tree, lock_txid)) .transpose() }) - .map(|(storage, _)| (storage.update_state(State::Idle), ())), - Err(e) => Err((self, e)), - } + .map_err(tx_err_into)?; + + self.state = State::Idle; + Ok(()) } - async fn commit(self) -> MutResult { + async fn commit(&mut self) -> Result<()> { let (txid, created_at) = match self.state { State::Transaction { txid, created_at, .. } => (txid, created_at), State::Idle => { - return Err(( - self, - Error::StorageMsg("no transaction to commit".to_owned()), - )); + return Err(Error::StorageMsg("no transaction to commit".to_owned())); } }; - if let Err(e) = lock::fetch(&self.tree, txid, created_at, self.tx_timeout) { - return Err((self, e)); - } + lock::fetch(&self.tree, txid, created_at, self.tx_timeout)?; - let (storage, _) = transaction!(self, move |tree| { lock::release(tree, txid) })?; - let gc = || { - if storage.tree.get("gc_lock").map_err(err_into)?.is_some() { - return Ok(()); - } + self.tree + .transaction(move |tree| lock::release(tree, txid)) + .map_err(tx_err_into)?; - storage.tree.insert("gc_lock", &[1]).map_err(err_into)?; + self.state = State::Idle; - let gc_result = storage.gc(); + if self.tree.get("gc_lock").map_err(err_into)?.is_some() { + return Ok(()); + } - storage.tree.remove("gc_lock").map_err(err_into)?; + self.tree.insert("gc_lock", &[1]).map_err(err_into)?; - gc_result - }; + let gc_result = self.gc(); - match gc() { - Ok(_) => Ok((storage, ())), - Err(e) => Err((storage, e)), - } + self.tree.remove("gc_lock").map_err(err_into)?; + + gc_result } } @@ -252,31 +214,19 @@ impl SledStorage { .map_err(tx_err_into) } - pub async fn check_and_retry( - self, + pub fn check_retry( + &mut self, tx_result: StdResult>, - retry_func: impl FnOnce(SledStorage) -> Fut, - ) -> MutResult - where - Fut: std::future::Future>, - { - match tx_result.map_err(tx_err_into) { - Ok(TxPayload::Success) => Ok((self, ())), - Ok(TxPayload::RollbackAndRetry(lock_txid)) => { - if let Err(err) = self.rollback_txid(lock_txid) { - return Err((self, err)); - }; + ) -> Result { + if let TxPayload::RollbackAndRetry(lock_txid) = tx_result.map_err(tx_err_into)? { + self.rollback_txid(lock_txid)?; + self.tree + .transaction(move |tree| lock::release(tree, lock_txid)) + .map_err(tx_err_into)?; - match self - .tree - .transaction(move |tree| lock::release(tree, lock_txid)) - .map_err(tx_err_into) - { - Ok(_) => retry_func(self).await, - Err(err) => Err((self, err)), - } - } - Err(err) => Err((self, err)), + Ok(true) + } else { + Ok(false) } } } diff --git a/storages/sled-storage/tests/export_and_import.rs b/storages/sled-storage/tests/export_and_import.rs index 2cb20cbea..7c5f3731d 100644 --- a/storages/sled-storage/tests/export_and_import.rs +++ b/storages/sled-storage/tests/export_and_import.rs @@ -20,7 +20,7 @@ fn export_and_import() { .unwrap(); let data1 = glue1.execute("SELECT * FROM Foo;").unwrap(); - let export = glue1.storage.unwrap().export().unwrap(); + let export = glue1.storage.export().unwrap(); let mut storage2 = SledStorage::try_from(config2).unwrap(); storage2.import(export).unwrap(); @@ -49,14 +49,14 @@ fn export_and_import_multiple_times() { .unwrap(); let data1 = glue1.execute("SELECT * FROM Foo;").unwrap(); - let export = glue1.storage.unwrap().export().unwrap(); + let export = glue1.storage.export().unwrap(); let mut storage2 = SledStorage::try_from(config2).unwrap(); storage2.import(export).unwrap(); let mut glue2 = Glue::new(storage2); let data2 = glue2.execute("SELECT * FROM Foo;").unwrap(); - let export2 = glue2.storage.unwrap().export().unwrap(); + let export2 = glue2.storage.export().unwrap(); assert_eq!(data1, data2); let mut storage3 = SledStorage::try_from(config3).unwrap(); diff --git a/storages/sled-storage/tests/sled_transaction.rs b/storages/sled-storage/tests/sled_transaction.rs index 16302ca1f..cc59e8650 100644 --- a/storages/sled-storage/tests/sled_transaction.rs +++ b/storages/sled-storage/tests/sled_transaction.rs @@ -382,8 +382,7 @@ async fn sled_transaction_gc() { // force change, txid -> 0 exec!(glue1 "BEGIN;"); - let mut storage = glue1.storage.unwrap(); - storage.state = State::Transaction { + glue1.storage.state = State::Transaction { txid: 0, created_at: SystemTime::now() .duration_since(UNIX_EPOCH) @@ -391,20 +390,15 @@ async fn sled_transaction_gc() { .as_millis(), autocommit: false, }; - let mut glue1 = Glue::new(storage); test!(glue1 "SELECT * FROM NewGarlic", Err(Error::StorageMsg("fetch failed - expired transaction has used (txid)".to_owned()))); assert_eq!( glue1 .storage - .unwrap() .insert_data("NewGarlic", vec![]) .await - .map(|(_, v)| v) - .map_err(|(_, e)| e), - Err(Error::StorageMsg( - "acquire failed - expired transaction has used (txid)".to_owned() - )), + .unwrap_err(), + Error::StorageMsg("acquire failed - expired transaction has used (txid)".to_owned()), ); } @@ -446,14 +440,10 @@ mod timeout_tests { glue1 .storage .clone() - .unwrap() .insert_data("TxGarlic", vec![]) .await - .map(|(_, v)| v) - .map_err(|(_, e)| e), - Err(Error::StorageMsg( - "acquire failed - expired transaction has used (timeout)".to_owned() - )), + .unwrap_err(), + Error::StorageMsg("acquire failed - expired transaction has used (timeout)".to_owned()), ); exec!(glue2 "BEGIN;"); diff --git a/storages/web-storage/src/lib.rs b/storages/web-storage/src/lib.rs index 26b38d2f2..72b07e689 100644 --- a/storages/web-storage/src/lib.rs +++ b/storages/web-storage/src/lib.rs @@ -6,7 +6,7 @@ use { gloo_storage::{errors::StorageError, LocalStorage, SessionStorage, Storage}, gluesql_core::{ data::{Key, Schema}, - result::{Error, MutResult, Result, TrySelf}, + result::{Error, Result}, store::{DataRow, RowIter, Store, StoreMut}, }, serde::{Deserialize, Serialize}, @@ -123,8 +123,9 @@ impl Store for WebStorage { } } -impl WebStorage { - pub fn insert_schema(&mut self, schema: &Schema) -> Result<()> { +#[async_trait(?Send)] +impl StoreMut for WebStorage { + async fn insert_schema(&mut self, schema: &Schema) -> Result<()> { let mut table_names: Vec = self.get(TABLE_NAMES_PATH)?.unwrap_or_default(); table_names.push(schema.table_name.clone()); @@ -132,7 +133,7 @@ impl WebStorage { self.set(format!("{}/{}", SCHEMA_PATH, schema.table_name), schema) } - pub fn delete_schema(&mut self, table_name: &str) -> Result<()> { + async fn delete_schema(&mut self, table_name: &str) -> Result<()> { let mut table_names: Vec = self.get(TABLE_NAMES_PATH)?.unwrap_or_default(); table_names .iter() @@ -142,11 +143,10 @@ impl WebStorage { self.set(TABLE_NAMES_PATH, table_names)?; self.delete(format!("{}/{}", SCHEMA_PATH, table_name)); self.delete(format!("{}/{}", DATA_PATH, table_name)); - Ok(()) } - pub fn append_data(&mut self, table_name: &str, new_rows: Vec) -> Result<()> { + async fn append_data(&mut self, table_name: &str, new_rows: Vec) -> Result<()> { let path = format!("{}/{}", DATA_PATH, table_name); let rows = self.get::>(&path)?.unwrap_or_default(); let new_rows = new_rows.into_iter().map(|row| { @@ -160,7 +160,7 @@ impl WebStorage { self.set(path, rows) } - pub fn insert_data(&mut self, table_name: &str, new_rows: Vec<(Key, DataRow)>) -> Result<()> { + async fn insert_data(&mut self, table_name: &str, new_rows: Vec<(Key, DataRow)>) -> Result<()> { let path = format!("{}/{}", DATA_PATH, table_name); let mut rows = self.get::>(&path)?.unwrap_or_default(); @@ -175,7 +175,7 @@ impl WebStorage { self.set(path, rows) } - pub fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { + async fn delete_data(&mut self, table_name: &str, keys: Vec) -> Result<()> { let path = format!("{}/{}", DATA_PATH, table_name); let mut rows = self.get::>(&path)?.unwrap_or_default(); @@ -189,33 +189,6 @@ impl WebStorage { } } -#[async_trait(?Send)] -impl StoreMut for WebStorage { - async fn insert_schema(mut self, schema: &Schema) -> MutResult { - WebStorage::insert_schema(&mut self, schema).try_self(self) - } - - async fn delete_schema(mut self, table_name: &str) -> MutResult { - WebStorage::delete_schema(&mut self, table_name).try_self(self) - } - - async fn append_data(mut self, table_name: &str, rows: Vec) -> MutResult { - WebStorage::append_data(&mut self, table_name, rows).try_self(self) - } - - async fn insert_data( - mut self, - table_name: &str, - rows: Vec<(Key, DataRow)>, - ) -> MutResult { - WebStorage::insert_data(&mut self, table_name, rows).try_self(self) - } - - async fn delete_data(mut self, table_name: &str, keys: Vec) -> MutResult { - WebStorage::delete_data(&mut self, table_name, keys).try_self(self) - } -} - #[cfg(feature = "alter-table")] impl gluesql_core::store::AlterTable for WebStorage {} #[cfg(feature = "index")] diff --git a/test-suite/Cargo.toml b/test-suite/Cargo.toml index 9b5d63a47..2b596c6a4 100644 --- a/test-suite/Cargo.toml +++ b/test-suite/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "gluesql-test-suite" version = "0.13.1" -edition = "2021" authors = ["Taehoon Moon "] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] gluesql-core = { path = "../core", version = "0.13.1" } diff --git a/test-suite/src/basic.rs b/test-suite/src/basic.rs index b371d7af1..605d2b782 100644 --- a/test-suite/src/basic.rs +++ b/test-suite/src/basic.rs @@ -1,6 +1,9 @@ use { crate::*, - gluesql_core::{prelude::Value::*, translate::TranslateError}, + gluesql_core::{ + prelude::{Payload, Value::*}, + translate::TranslateError, + }, }; test_case!(basic, async move { @@ -20,6 +23,7 @@ CREATE TABLE TestA ( name TEXT )"# ); + run!("CREATE TABLE EmptyTest"); run!("INSERT INTO Test (id, num, name) VALUES (1, 2, 'Hello')"); run!("INSERT INTO Test (id, num, name) VALUES (1, 9, 'World')"); @@ -47,6 +51,11 @@ CREATE TABLE TestA ( )) ); + test!("SELECT * FROM EmptyTest", Ok(Payload::SelectMap(vec![]))); + test!( + "SELECT * FROM (SELECT * FROM EmptyTest) AS Empty", + Ok(Payload::SelectMap(vec![])) + ); count!(4, "SELECT * FROM Test"); run!("UPDATE Test SET id = 2"); diff --git a/test-suite/src/column_alias.rs b/test-suite/src/column_alias.rs new file mode 100644 index 000000000..fdd03afa8 --- /dev/null +++ b/test-suite/src/column_alias.rs @@ -0,0 +1,130 @@ +use { + crate::*, + gluesql_core::{ + executor::FetchError, + prelude::{Payload, Value::*}, + }, +}; + +test_case!(column_alias, async move { + let test_cases = [ + ( + "CREATE TABLE InnerTable ( + id INTEGER, + name TEXT + )", + Ok(Payload::Create), + ), + ( + "CREATE TABLE User ( + id INTEGER, + name TEXT + )", + Ok(Payload::Create), + ), + ("CREATE TABLE EmptyTable", Ok(Payload::Create)), + ( + "INSERT INTO InnerTable VALUES (1, 'GLUE'), (2, 'SQL'), (3, 'SQL')", + Ok(Payload::Insert(3)), + ), + ( + "INSERT INTO User VALUES (1, 'Taehoon'), (2, 'Mike'), (3, 'Jorno')", + Ok(Payload::Insert(3)), + ), + ( + "SELECT * FROM InnerTable", + Ok(select!( + id | name + I64 | Str; + 1 "GLUE".to_owned(); + 2 "SQL".to_owned(); + 3 "SQL".to_owned() + )), + ), + ( + // column alias with wildcard + "SELECT * FROM User AS Table(a, b)", + Ok(select!( + a | b + I64 | Str; + 1 "Taehoon".to_owned(); + 2 "Mike".to_owned(); + 3 "Jorno".to_owned() + )), + ), + ( + // partial column alias + "SELECT * FROM User AS Table(a)", + Ok(select!( + a | name + I64 | Str; + 1 "Taehoon".to_owned(); + 2 "Mike".to_owned(); + 3 "Jorno".to_owned() + )), + ), + ( + // column alias (non-wildcard) + "SELECT a FROM User AS Table(a, b)", + Ok(select!( a; I64; 1; 2; 3)), + ), + ( + // too many column alias + "Select * from User as Table(a, b, c)", + Err(FetchError::TooManyColumnAliases("User".to_owned(), 2, 3).into()), + ), + // InlineView + ( + // column alias with wildcard + "SELECT * FROM (SELECT * FROM InnerTable) AS InlineView(a, b)", + Ok(select!( + a | b + I64 | Str; + 1 "GLUE".to_owned(); + 2 "SQL".to_owned(); + 3 "SQL".to_owned() + )), + ), + ( + // partial column alias + "SELECT * FROM (SELECT * FROM InnerTable) AS InlineView(a)", + Ok(select!( + a | name + I64 | Str; + 1 "GLUE".to_owned(); + 2 "SQL".to_owned(); + 3 "SQL".to_owned() + )), + ), + ( + // too many column alias + "SELECT * FROM (SELECT * FROM InnerTable) AS InlineView(a, b, c)", + Err(FetchError::TooManyColumnAliases("InlineView".into(), 2, 3).into()), + ), + ( + "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS Derived(id)", + Ok(select!( + id | column2; + I64 | Str; + 1 "a".to_owned(); + 2 "b".to_owned() + )), + ), + ( + "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS Derived(id, name)", + Ok(select!( + id | name; + I64 | Str; + 1 "a".to_owned(); + 2 "b".to_owned() + )), + ), + ( + "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS Derived(id, name, dummy)", + Err(FetchError::TooManyColumnAliases("Derived".into(), 2, 3).into()), + ), + ]; + for (sql, expected) in test_cases { + test!(sql, expected); + } +}); diff --git a/test-suite/src/concat.rs b/test-suite/src/concat.rs index 87bb758f7..f19604441 100644 --- a/test-suite/src/concat.rs +++ b/test-suite/src/concat.rs @@ -88,4 +88,40 @@ test_case!(concat, async move { Str("1123Bar".to_owned()) Str("1TRUE3.5Foo".to_owned()) Null )) ); + + test!( + "SELECT 'sand' || SUBSTR('swich', 2) AS test FROM Concat;", + Ok(select!( + test + Str; + "sandwich".to_owned() + )) + ); + + test!( + "SELECT SUBSTR('ssand', 2) || 'wich' AS test from Concat;", + Ok(select!( + test + Str; + "sandwich".to_owned() + )) + ); + + test!( + "SELECT LOWER('SAND') || SUBSTR('swich', 2) AS test FROM Concat;", + Ok(select!( + test + Str; + "sandwich".to_owned() + )) + ); + + test!( + "SELECT SUBSTR('ssand', 2) || LOWER('WICH') AS test FROM Concat;", + Ok(select!( + test + Str; + "sandwich".to_owned() + )) + ); }); diff --git a/test-suite/src/data_type/inet.rs b/test-suite/src/data_type/inet.rs new file mode 100644 index 000000000..e7c58e38e --- /dev/null +++ b/test-suite/src/data_type/inet.rs @@ -0,0 +1,62 @@ +use { + crate::*, + gluesql_core::{data::ValueError, executor::Payload, prelude::Value::Inet}, + std::{net::IpAddr, str::FromStr}, +}; + +test_case!(inet, async move { + let inet = |v| IpAddr::from_str(v).unwrap(); + + let test_cases = [ + ("CREATE TABLE computer (ip INET)", Ok(Payload::Create)), + ( + "INSERT INTO computer VALUES + ('::1'), + ('127.0.0.1'), + ('0.0.0.0'), + (4294967295), + (9876543210); + ", + Ok(Payload::Insert(5)), + ), + ( + "SELECT * FROM computer", + Ok(select!( + ip + Inet; + inet("::1"); + inet("127.0.0.1"); + inet("0.0.0.0"); + inet("255.255.255.255"); + inet("::2:4cb0:16ea") + )), + ), + ( + "SELECT * FROM computer WHERE ip > '127.0.0.1'", + Ok(select!( + ip + Inet; + inet("::1"); + inet("255.255.255.255"); + inet("::2:4cb0:16ea") + )), + ), + ( + "SELECT * FROM computer WHERE ip = '127.0.0.1'", + Ok(select!( + ip + Inet; + inet("127.0.0.1") + )), + ), + ("INSERT INTO computer VALUES (0)", Ok(Payload::Insert(1))), + ( + r#"INSERT INTO computer VALUES ('127.0.0.0.1')"#, + Err(ValueError::FailedToParseInetString("127.0.0.0.1".to_owned()).into()), + ), + ]; + + for (sql, expected) in test_cases { + test!(sql, expected); + } +}); diff --git a/test-suite/src/data_type/int64.rs b/test-suite/src/data_type/int64.rs index 1e29abadf..04a0db983 100644 --- a/test-suite/src/data_type/int64.rs +++ b/test-suite/src/data_type/int64.rs @@ -2,10 +2,13 @@ use { crate::*, gluesql_core::{ data::ValueError, - prelude::{DataType, Payload, Value::*}, + prelude::{DataType, Value::*}, }, }; +#[cfg(not(target_arch = "wasm32"))] +use gluesql_core::prelude::Payload; + test_case!(int64, async move { run!( "CREATE TABLE Item ( @@ -115,6 +118,7 @@ test_case!(int64, async move { ); // try inserting i64 max and i64 min + #[cfg(not(target_arch = "wasm32"))] test!( &format!("INSERT INTO Item VALUES ({}, {})", i64::MAX, i64::MIN), Ok(Payload::Insert(1)) diff --git a/test-suite/src/data_type/mod.rs b/test-suite/src/data_type/mod.rs index c431ce58d..c79e77aad 100644 --- a/test-suite/src/data_type/mod.rs +++ b/test-suite/src/data_type/mod.rs @@ -1,6 +1,7 @@ pub mod bytea; pub mod date; pub mod decimal; +pub mod inet; pub mod int128; pub mod int16; pub mod int32; diff --git a/test-suite/src/function/cast.rs b/test-suite/src/function/cast.rs index 6dd40c1b8..b2c1ef5c5 100644 --- a/test-suite/src/function/cast.rs +++ b/test-suite/src/function/cast.rs @@ -56,6 +56,10 @@ test_case!(cast_literal, async move { "SELECT CAST('1' AS INTEGER) AS cast FROM Item", Ok(select!(cast I64; 1)), ), + ( + "SELECT CAST(SUBSTR('123', 2, 3) AS INTEGER) AS cast FROM Item", + Ok(select!(cast I64; 23)), + ), ( "SELECT CAST('foo' AS INTEGER) AS cast FROM Item", Err(ValueError::LiteralCastFromTextToIntegerFailed("foo".to_owned()).into()), diff --git a/test-suite/src/function/ltrim_rtrim.rs b/test-suite/src/function/ltrim_rtrim.rs index b211be5d6..da102329c 100644 --- a/test-suite/src/function/ltrim_rtrim.rs +++ b/test-suite/src/function/ltrim_rtrim.rs @@ -19,6 +19,24 @@ test_case!(ltrim_rtrim, async move { "INSERT INTO Item VALUES (' zzzytest'), ('testxxzx ')", Ok(Payload::Insert(2)), ), + ( + "SELECT LTRIM('x', 'xyz') AS test from Item;", + Ok(select!( + "test" + Str; + "".to_owned(); + "".to_owned() + )), + ), + ( + "SELECT LTRIM('txu', 'xyz') AS test from Item;", + Ok(select!( + "test" + Str; + "txu".to_owned(); + "txu".to_owned() + )), + ), ( "SELECT LTRIM(name) AS test FROM Item", Ok(select!( @@ -28,6 +46,15 @@ test_case!(ltrim_rtrim, async move { "testxxzx ".to_owned() )), ), + ( + "SELECT LTRIM(RTRIM('GlueSQLABC', 'ABC')) AS test FROM Item;", + Ok(select!( + "test" + Str; + "GlueSQL".to_owned(); + "GlueSQL".to_owned() + )), + ), ( "SELECT LTRIM(name, ' xyz') AS test FROM Item", Ok(select!( @@ -55,6 +82,42 @@ test_case!(ltrim_rtrim, async move { "test".to_owned() )), ), + ( + "SELECT RTRIM('x', 'xyz') AS test from Item;", + Ok(select!( + "test" + Str; + "".to_owned(); + "".to_owned() + )), + ), + ( + "SELECT RTRIM('tuv', 'xyz') AS test from Item;", + Ok(select!( + "test" + Str; + "tuv".to_owned(); + "tuv".to_owned() + )), + ), + ( + "SELECT RTRIM('txu', 'xyz') AS test from Item;", + Ok(select!( + "test" + Str; + "txu".to_owned(); + "txu".to_owned() + )), + ), + ( + "SELECT RTRIM('xux', 'xyz') AS test from Item;", + Ok(select!( + "test" + Str; + "xu".to_owned(); + "xu".to_owned() + )), + ), ( "SELECT LTRIM(1) AS test FROM Item", Err(EvaluateError::FunctionRequiresStringValue("LTRIM".to_owned()).into()), @@ -76,6 +139,10 @@ test_case!(ltrim_rtrim, async move { Ok(Payload::Create), ), ("INSERT INTO NullTest VALUES (null)", Ok(Payload::Insert(1))), + ( + "SELECT LTRIM('name', NULL) AS test FROM NullTest", + Ok(select_with_null!(test; Value::Null)), + ), ( "SELECT LTRIM(name) AS test FROM NullTest", Ok(select_with_null!(test; Value::Null)), @@ -84,6 +151,10 @@ test_case!(ltrim_rtrim, async move { "SELECT RTRIM(name) AS test FROM NullTest", Ok(select_with_null!(test; Value::Null)), ), + ( + "SELECT RTRIM('name', NULL) AS test FROM NullTest", + Ok(select_with_null!(test; Value::Null)), + ), ( "SELECT LTRIM(NULL, '123') AS test FROM NullTest", Ok(select_with_null!(test; Value::Null)), diff --git a/test-suite/src/function/mod.rs b/test-suite/src/function/mod.rs index c3d19dad8..1a815ed85 100644 --- a/test-suite/src/function/mod.rs +++ b/test-suite/src/function/mod.rs @@ -22,6 +22,7 @@ pub mod now; pub mod pi; pub mod position; pub mod radians; +pub mod rand; pub mod repeat; pub mod reverse; pub mod round; diff --git a/test-suite/src/function/rand.rs b/test-suite/src/function/rand.rs new file mode 100644 index 000000000..369d25517 --- /dev/null +++ b/test-suite/src/function/rand.rs @@ -0,0 +1,59 @@ +use { + crate::*, + gluesql_core::{ + executor::EvaluateError, + prelude::{Payload, Value::*}, + translate::TranslateError, + }, +}; + +test_case!(rand, async move { + let test_cases = [ + ( + "CREATE TABLE SingleItem (qty INTEGER DEFAULT ROUND(RAND()*100))", + Ok(Payload::Create), + ), + ( + r#"INSERT INTO SingleItem VALUES (ROUND(RAND(1)*100))"#, + Ok(Payload::Insert(1)), + ), + ( + "SELECT RAND(123) AS rand1, RAND(789.0) AS rand2 FROM SingleItem", + Ok(select!( + rand1 | rand2 + F64 | F64; + 0.17325464426155657 0.9635218234007941 + )), + ), + ( + "SELECT RAND('string') AS rand FROM SingleItem", + Err(EvaluateError::FunctionRequiresFloatValue(String::from("RAND")).into()), + ), + ( + "SELECT RAND(NULL) AS rand FROM SingleItem", + Ok(select_with_null!(rand; Null)), + ), + ( + "SELECT RAND(TRUE) AS rand FROM SingleItem", + Err(EvaluateError::FunctionRequiresFloatValue(String::from("RAND")).into()), + ), + ( + "SELECT RAND(FALSE) AS rand FROM SingleItem", + Err(EvaluateError::FunctionRequiresFloatValue(String::from("RAND")).into()), + ), + ( + "SELECT RAND('string', 'string2') AS rand FROM SingleItem", + Err(TranslateError::FunctionArgsLengthNotWithinRange { + name: "RAND".to_owned(), + expected_minimum: 0, + expected_maximum: 1, + found: 2, + } + .into()), + ), + ]; + + for (sql, expected) in test_cases { + test!(sql, expected); + } +}); diff --git a/test-suite/src/function/substr.rs b/test-suite/src/function/substr.rs index da28e468f..4d1863171 100644 --- a/test-suite/src/function/substr.rs +++ b/test-suite/src/function/substr.rs @@ -16,9 +16,9 @@ test_case!(substr, async move { "INSERT INTO Item VALUES ('Blop mc blee'), ('B'), ('Steven the &long named$ folken!')", Ok(Payload::Insert(3)), ), - ("CREATE TABLE SingleItem (id INTEGER)", Ok(Payload::Create)), + ("CREATE TABLE SingleItem (food TEXT)", Ok(Payload::Create)), ( - r#"INSERT INTO SingleItem VALUES (0)"#, + "INSERT INTO SingleItem VALUES (SUBSTR('LobSter',1))", Ok(Payload::Insert(1)), ), ( @@ -38,7 +38,7 @@ test_case!(substr, async move { Ok(Payload::Insert(1)), ), ( - r#"SELECT SUBSTR(name, 1) AS test FROM Item"#, + r#"SELECT SUBSTR(SUBSTR(name, 1), 1) AS test FROM Item"#, Ok(select!( "test" Str; @@ -47,6 +47,93 @@ test_case!(substr, async move { "Steven the &long named$ folken!".to_owned() )), ), + ( + "SELECT * FROM Item WHERE name = SUBSTR('ABC', 2, 1)", + Ok(select!( + "name" + Str; + "B".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE SUBSTR(name, 1, 1) = 'B'", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "B".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE 'B' = SUBSTR(name, 1, 1)", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "B".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE SUBSTR(name, 1, 1) = UPPER('b')", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "B".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE SUBSTR(name, 1, 4) = SUBSTR('Blop', 1)", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE SUBSTR(name, 1, 4) > SUBSTR('Blop', 1)", + Ok(select!( + "name" + Str; + "Steven the &long named$ folken!".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE SUBSTR(name, 1, 4) > 'B'", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "Steven the &long named$ folken!".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE 'B' < SUBSTR(name, 1, 4)", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "Steven the &long named$ folken!".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE SUBSTR(name, 1, 4) > UPPER('b')", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "Steven the &long named$ folken!".to_owned() + )), + ), + ( + "SELECT * FROM Item WHERE UPPER('b') < SUBSTR(name, 1, 4)", + Ok(select!( + "name" + Str; + "Blop mc blee".to_owned(); + "Steven the &long named$ folken!".to_owned() + )), + ), ( r#"SELECT SUBSTR(name, 2) AS test FROM Item"#, Ok(select!( @@ -123,6 +210,14 @@ test_case!(substr, async move { "AB".to_owned() )), ), + ( + "SELECT SUBSTR(SUBSTR('ABC', 2, 3), 1, 1) AS test FROM SingleItem", + Ok(select!( + "test" + Str; + "B".to_owned() + )), + ), ( "SELECT SUBSTR('ABC', -1, NULL) AS test FROM SingleItem", Ok(select_with_null!(test; Null)), @@ -135,6 +230,10 @@ test_case!(substr, async move { r#"SELECT SUBSTR('Words', number) AS test FROM NullNumber"#, Ok(select_with_null!(test; Null)), ), + ( + "SELECT * FROM SingleItem WHERE TRUE AND SUBSTR('wine',2,3)", + Err(EvaluateError::BooleanTypeRequired("ine".to_owned()).into()), + ), ( r#"SELECT SUBSTR(1, 1) AS test FROM SingleItem"#, Err(EvaluateError::FunctionRequiresStringValue("SUBSTR".to_owned()).into()), @@ -147,6 +246,26 @@ test_case!(substr, async move { r#"SELECT SUBSTR('Words', 1, -4) AS test FROM SingleItem"#, Err(EvaluateError::NegativeSubstrLenNotAllowed.into()), ), + ( + r#"SELECT SUBSTR('123', 2, 3) - '3' AS test FROM SingleItem"#, + Err(EvaluateError::UnsupportedBinaryArithmetic( + "StrSlice { source: \"123\", range: 1..3 }".to_owned(), + "Literal(Text(\"3\"))".to_owned(), + ) + .into()), + ), + ( + r#"SELECT +SUBSTR('123', 2, 3) AS test FROM SingleItem"#, + Err(EvaluateError::UnsupportedUnaryPlus("23".to_owned()).into()), + ), + ( + r#"SELECT -SUBSTR('123', 2, 3) AS test FROM SingleItem"#, + Err(EvaluateError::UnsupportedUnaryMinus("23".to_owned()).into()), + ), + ( + r#"SELECT SUBSTR('123', 2, 3)! AS test FROM SingleItem"#, + Err(EvaluateError::UnsupportedUnaryFactorial("23".to_owned()).into()), + ), ]; for (sql, expected) in test_cases { test!(sql, expected); diff --git a/test-suite/src/function/trim.rs b/test-suite/src/function/trim.rs index f81b08f11..126f448c5 100644 --- a/test-suite/src/function/trim.rs +++ b/test-suite/src/function/trim.rs @@ -53,6 +53,10 @@ test_case!(trim, async move { Value::Null )), ), + ( + "SELECT TRIM(BOTH NULL FROM 'name') AS test", + Ok(select_with_null!(test; Value::Null)), + ), ( "SELECT TRIM(TRAILING NULL FROM name) FROM NullName;", Ok(select_with_null!( @@ -69,7 +73,7 @@ test_case!(trim, async move { ), ("CREATE TABLE Test (name TEXT)", Ok(Payload::Create)), ( - "INSERT INTO Test VALUES + "INSERT INTO Test VALUES (' blank '), ('xxxyzblankxyzxx'), ('xxxyzblank '), @@ -129,6 +133,26 @@ test_case!(trim, async move { "hello".to_owned() "hello ".to_owned() " hello".to_owned() )), ), + ( + "SELECT + TRIM(BOTH TRIM(BOTH ' potato ')) AS Case1, + TRIM('xyz' FROM 'x') AS Case2, + TRIM(TRAILING 'xyz' FROM 'xx') AS Case3 + ", + Ok(select!( + Case1 | Case2 | Case3 + Value::Str | Value::Str | Value::Str; + "potato".to_owned() "".to_owned() "".to_owned() + )), + ), + ( + "SELECT TRIM('1' FROM 1) AS test FROM Test", + Err(EvaluateError::FunctionRequiresStringValue("TRIM".to_owned()).into()), + ), + ( + "SELECT TRIM(1 FROM TRIM('t' FROM 'tartare')) AS test FROM Test", + Err(EvaluateError::FunctionRequiresStringValue("TRIM".to_owned()).into()), + ), ]; for (sql, expected) in test_cases { diff --git a/test-suite/src/join.rs b/test-suite/src/join.rs index 37c9e0e78..4140a7192 100644 --- a/test-suite/src/join.rs +++ b/test-suite/src/join.rs @@ -252,10 +252,10 @@ test_case!(project, async move { test!(sql, Ok(expected)); // To test `PlanError` while using `JOIN` - run!("CREATE TABLE users (id INTEGER, name TEXT);"); - run!("INSERT INTO users (id, name) VALUES (1, 'Harry');"); - run!("CREATE TABLE testers (id INTEGER, nickname TEXT);"); - run!("INSERT INTO testers (id, nickname) VALUES (1, 'Ron');"); + run!("CREATE TABLE Users (id INTEGER, name TEXT);"); + run!("INSERT INTO Users (id, name) VALUES (1, 'Harry');"); + run!("CREATE TABLE Testers (id INTEGER, nickname TEXT);"); + run!("INSERT INTO Testers (id, nickname) VALUES (1, 'Ron');"); let error_cases = [ ( @@ -267,7 +267,20 @@ test_case!(project, async move { TranslateError::UnsupportedJoinOperator("CrossJoin".to_owned()).into(), ), ( - "SELECT id FROM users JOIN testers ON users.id = testers.id;", + "SELECT id FROM Users JOIN Testers ON Users.id = Testers.id;", + PlanError::ColumnReferenceAmbiguous("id".to_owned()).into(), + ), + ( + // Ambiguous column should return error even with identical table join + "SELECT id FROM Users A JOIN Users B on A.id = B.id", + PlanError::ColumnReferenceAmbiguous("id".to_owned()).into(), + ), + ( + "INSERT INTO Users SELECT id FROM Users A JOIN Users B on A.id = B.id", + PlanError::ColumnReferenceAmbiguous("id".to_owned()).into(), + ), + ( + "CREATE TABLE Ids AS SELECT id FROM Users A JOIN Users B on A.id = B.id", PlanError::ColumnReferenceAmbiguous("id".to_owned()).into(), ), ( diff --git a/test-suite/src/lib.rs b/test-suite/src/lib.rs index 0bf035c13..0e07e59bc 100644 --- a/test-suite/src/lib.rs +++ b/test-suite/src/lib.rs @@ -6,6 +6,7 @@ pub mod arithmetic; pub mod ast_builder; pub mod basic; pub mod case; +pub mod column_alias; pub mod concat; pub mod data_type; pub mod default; @@ -112,6 +113,7 @@ macro_rules! generate_store_tests { glue!(function_abs, function::abs::abs); glue!(function_ceil, function::ceil::ceil); glue!(function_round, function::round::round); + glue!(function_rand, function::rand::rand); glue!(function_floor, function::floor::floor); glue!(function_format, function::format::format); glue!(function_ln, function::exp_log::ln); @@ -152,6 +154,7 @@ macro_rules! generate_store_tests { glue!(list, data_type::list::list); glue!(map, data_type::map::map); glue!(bytea, data_type::bytea::bytea); + glue!(inet, data_type::inet::inet); glue!(synthesize, synthesize::synthesize); glue!(validate_unique, validate::unique::unique); glue!(validate_types, validate::types::types); @@ -171,6 +174,7 @@ macro_rules! generate_store_tests { ); glue!(type_match, type_match::type_match); glue!(dictionary, dictionary::dictionary); + glue!(column_alias, column_alias::column_alias); // ast-builder glue!(ast_builder_basic, ast_builder::basic::basic); diff --git a/test-suite/src/like_ilike.rs b/test-suite/src/like_ilike.rs index 126bfc85b..82e4aaa8c 100644 --- a/test-suite/src/like_ilike.rs +++ b/test-suite/src/like_ilike.rs @@ -44,7 +44,24 @@ test_case!(like_ilike, async move { let test_cases = [ (2, "SELECT name FROM Item WHERE name LIKE '_a%'"), (2, "SELECT name FROM Item WHERE name LIKE '%r%'"), - (2, "SELECT name FROM Item WHERE name LIKE '%a'"), + (2, "SELECT name FROM Item WHERE SUBSTR(name, 1) LIKE '%a'"), + (0, "SELECT name FROM Item WHERE 'name' LIKE SUBSTR('%a', 1)"), + ( + 2, + "SELECT name FROM Item WHERE SUBSTR(name, 1) LIKE SUBSTR('%a', 1)", + ), + ( + 2, + "SELECT name FROM Item WHERE SUBSTR(name, 1) LIKE SUBSTR('%a', 1)", + ), + ( + 2, + "SELECT name FROM Item WHERE LOWER(name) LIKE SUBSTR('%a', 1)", + ), + ( + 2, + "SELECT name FROM Item WHERE SUBSTR(name, 1) LIKE '%' || LOWER('A')", + ), (5, "SELECT name FROM Item WHERE name LIKE '%%'"), (0, "SELECT name FROM Item WHERE name LIKE 'g%'"), (2, "SELECT name FROM Item WHERE name ILIKE '_A%'"), diff --git a/test-suite/src/nullable.rs b/test-suite/src/nullable.rs index a8c5c90ad..e6948c512 100644 --- a/test-suite/src/nullable.rs +++ b/test-suite/src/nullable.rs @@ -6,11 +6,12 @@ use { test_case!(nullable, async move { run!( " -CREATE TABLE Test ( - id INTEGER NULL, - num INTEGER NOT NULL, - name TEXT -)" + CREATE TABLE Test ( + id INTEGER NULL, + num INTEGER NOT NULL, + name TEXT + ) + " ); run!( " @@ -18,7 +19,7 @@ CREATE TABLE Test ( (NULL, 2, 'Hello'), ( 1, 9, 'World'), ( 3, 4, 'Great'); - " + " ); let test_cases = [ @@ -45,6 +46,12 @@ CREATE TABLE Test ( Null I64(2) ), ), + ( + "SELECT name FROM Test WHERE SUBSTR(name, 1) IS NULL", + select!( + name; + ), + ), ( "SELECT id, num FROM Test WHERE id IS NOT NULL", select_with_null!( diff --git a/test-suite/src/primary_key.rs b/test-suite/src/primary_key.rs index 401fa16b7..7df2264ca 100644 --- a/test-suite/src/primary_key.rs +++ b/test-suite/src/primary_key.rs @@ -107,7 +107,14 @@ test_case!(primary_key, async move { 3 "foo".to_owned() )) ); - + run!( + " + CREATE TABLE Strslice ( + name TEXT PRIMARY KEY + ); + " + ); + run!("INSERT INTO Strslice VALUES (SUBSTR(SUBSTR('foo', 1), 1));"); // PRIMARY KEY includes UNIQUE constraint test!( "INSERT INTO Allegro VALUES (1, 'another hello');", diff --git a/test-suite/src/schemaless/error.rs b/test-suite/src/schemaless/error.rs index 149d970a6..4c121c54d 100644 --- a/test-suite/src/schemaless/error.rs +++ b/test-suite/src/schemaless/error.rs @@ -31,6 +31,13 @@ test_case!(error, async move { ) .as_str()); + run!("CREATE TABLE Food"); + run!(format!( + "INSERT INTO Food VALUES (SUBSTR(SUBSTR(' hi{}', 4), 1));", + json!({ "id": 1, "name": "meat", "weight": 10 }), + ) + .as_str()); + test!( r#" INSERT INTO Item diff --git a/test-suite/src/tester/mod.rs b/test-suite/src/tester/mod.rs index bf6ec6dcf..832eb411d 100644 --- a/test-suite/src/tester/mod.rs +++ b/test-suite/src/tester/mod.rs @@ -92,12 +92,10 @@ pub async fn run( glue: &mut Glue, indexes: Option>, ) -> Result { - let storage = glue.storage.as_ref().unwrap(); - println!("[SQL] {}", sql); let parsed = parse(sql)?; let statement = translate(&parsed[0])?; - let statement = plan(storage, statement).await?; + let statement = plan(&glue.storage, statement).await?; test_indexes(&statement, indexes); @@ -239,8 +237,6 @@ macro_rules! test_case { macro_rules! schema { ($table_name: literal) => { glue.storage - .as_ref() - .expect("storage is empty") .fetch_schema($table_name) .await .expect("error fetching schema") diff --git a/test-suite/src/update.rs b/test-suite/src/update.rs index c09495e70..a553d85eb 100644 --- a/test-suite/src/update.rs +++ b/test-suite/src/update.rs @@ -60,6 +60,10 @@ test_case!(update, async move { "UPDATE TableA SET id = 4 WHERE num = 9", Ok(Payload::Update(1)) ), + ( + "UPDATE TableA SET name = SUBSTR('John', 1) WHERE num = 9", + Ok(Payload::Update(1)) + ), ( "SELECT id, num FROM TableA", Ok(select!(id | num; I64 | I64; 2 2; 4 9; 2 4; 2 7)) diff --git a/test-suite/src/values.rs b/test-suite/src/values.rs index 29d661ba3..9b9846459 100644 --- a/test-suite/src/values.rs +++ b/test-suite/src/values.rs @@ -4,7 +4,7 @@ use { gluesql_core::{ ast::DataType::{Boolean, Int, Text}, data::{Literal, ValueError}, - executor::{FetchError, InsertError, SelectError}, + executor::{InsertError, SelectError}, prelude::{DataType, Payload, Value::*}, }, std::borrow::Cow, @@ -137,28 +137,6 @@ test_case!(values, async move { 2 "b".to_owned() )), ), - ( - "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS Derived(id)", - Ok(select!( - id | column2; - I64 | Str; - 1 "a".to_owned(); - 2 "b".to_owned() - )), - ), - ( - "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS Derived(id, name)", - Ok(select!( - id | name; - I64 | Str; - 1 "a".to_owned(); - 2 "b".to_owned() - )), - ), - ( - "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS Derived(id, name, dummy)", - Err(FetchError::TooManyColumnAliases("Derived".into(), 2, 3).into()), - ), ( "INSERT INTO Items (id) VALUES (1);", Ok(Payload::Insert(1)) diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 831eeea3e..f99c5f7b5 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "gluesql-utils" version = "0.13.0" -edition = "2021" authors = ["Taehoon Moon "] -description = "GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!" -license = "Apache-2.0" -repository = "https://github.com/gluesql/gluesql" -documentation = "https://docs.rs/gluesql/" +edition.workspace = true +description.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true [dependencies] indexmap = "1" From dcd7d59dd777307296a1b986d8123895b2cd725d Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 10 Feb 2023 16:35:08 +0900 Subject: [PATCH 40/62] style: cargo fmt --- core/src/data/value/convert.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index ec894cbb6..421133071 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -689,16 +689,16 @@ impl TryFrom<&Value> for u32 { impl TryFrom<&Value> for u128 { type Error = Error; -// impl TryFrom<&Value> for u128 { -// type Error = Error; - -// fn try_from(v: &Value) -> Result { -// match v { -// Value::Uuid(value) => Ok(*value), -// _ => Err(ValueError::ImpossibleCast.into()), -// } -// } -// } + // impl TryFrom<&Value> for u128 { + // type Error = Error; + + // fn try_from(v: &Value) -> Result { + // match v { + // Value::Uuid(value) => Ok(*value), + // _ => Err(ValueError::ImpossibleCast.into()), + // } + // } + // } fn try_from(v: &Value) -> Result { match v { Value::Uuid(value) => Ok(*value), From 5a9db7f4a8dea9bb83fdfd183a81dcb09d4896ee Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 10 Feb 2023 16:52:52 +0900 Subject: [PATCH 41/62] fix: fix merge error --- core/src/data/bigdecimal_ext.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/core/src/data/bigdecimal_ext.rs b/core/src/data/bigdecimal_ext.rs index a3999770a..955ecd6ad 100644 --- a/core/src/data/bigdecimal_ext.rs +++ b/core/src/data/bigdecimal_ext.rs @@ -10,9 +10,7 @@ pub trait BigDecimalExt { fn to_u16(&self) -> Option; fn to_u32(&self) -> Option; fn to_u128(&self) -> Option; - fn to_u32(&self) -> Option; fn to_u64(&self) -> Option; - fn to_u128(&self) -> Option; fn to_f64(&self) -> Option; } @@ -59,16 +57,6 @@ impl BigDecimalExt for BigDecimal { false => None, } } - fn to_u128(&self) -> Option { - match self.is_integer() { - true => bigdecimal::ToPrimitive::to_u128(self), - false => None, - } - } - fn to_u32(&self) -> Option { - self.is_integer() - .then(|| bigdecimal::ToPrimitive::to_u32(self))? - } fn to_u128(&self) -> Option { self.is_integer() .then(|| bigdecimal::ToPrimitive::to_u128(self))? From fd6a036220a71c7a776f1d27c2e376218da1c4b3 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 10 Feb 2023 19:15:15 +0900 Subject: [PATCH 42/62] fix : try merging the main again --- core/src/data/value/convert.rs | 96 ++++++++++------------------------ 1 file changed, 27 insertions(+), 69 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 421133071..b501b680f 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -412,6 +412,7 @@ impl TryFrom<&Value> for u32 { .parse::() .map_err(|_| ValueError::ImpossibleCast)?, Value::Decimal(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, + Value::Inet(IpAddr::V4(v)) => Ok(u32::from(*v)), Value::Date(_) | Value::Timestamp(_) | Value::Time(_) @@ -481,6 +482,7 @@ impl TryFrom<&Value> for u128 { .map_err(|_| ValueError::ImpossibleCast)?, Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Uuid(value) => *value, + Value::Inet(IpAddr::V6(v)) => Ok(u128::from(*v)), Value::Date(_) | Value::Timestamp(_) | Value::Time(_) @@ -676,39 +678,6 @@ impl TryFrom<&Value> for Interval { } } -impl TryFrom<&Value> for u32 { - type Error = Error; - - fn try_from(v: &Value) -> Result { - match v { - Value::Inet(IpAddr::V4(v)) => Ok(u32::from(*v)), - _ => Err(ValueError::ImpossibleCast.into()), - } - } -} - -impl TryFrom<&Value> for u128 { - type Error = Error; - // impl TryFrom<&Value> for u128 { - // type Error = Error; - - // fn try_from(v: &Value) -> Result { - // match v { - // Value::Uuid(value) => Ok(*value), - // _ => Err(ValueError::ImpossibleCast.into()), - // } - // } - // } - fn try_from(v: &Value) -> Result { - match v { - Value::Uuid(value) => Ok(*value), - Value::Str(value) => parse_uuid(value), - Value::Inet(IpAddr::V6(v)) => Ok(u128::from(*v)), - _ => Err(ValueError::ImpossibleCast.into()), - } - } -} - impl TryFrom<&Value> for IpAddr { type Error = Error; @@ -1371,6 +1340,14 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); + assert_eq!( + u32::try_from(&Value::Inet(IpAddr::from_str("0.0.0.0").unwrap())), + Ok(u32::from(Ipv4Addr::from(0))) + ); + assert_eq!( + u32::try_from(&Value::Inet(IpAddr::from_str("::0").unwrap())), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1486,6 +1463,23 @@ mod tests { let uuid = 195965723427462096757863453463987888808; assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); + + let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; + assert_eq!( + u128::try_from(&Value::Str(uuid.to_owned())), + parse_uuid(uuid) + ); + + let ip = Ipv6Addr::from(9876543210); + assert_eq!( + u128::try_from(&Value::Inet(IpAddr::V6(ip))), + Ok(u128::from(ip)) + ); + + assert_eq!( + u128::try_from(&Value::Date(date(2021, 11, 20))), + Err(ValueError::ImpossibleCast.into()) + ); } #[test] @@ -1675,42 +1669,6 @@ mod tests { ); } - #[test] - fn try_into_u32() { - assert_eq!( - u32::try_from(&Value::Inet(IpAddr::from_str("0.0.0.0").unwrap())), - Ok(u32::from(Ipv4Addr::from(0))) - ); - assert_eq!( - u32::try_from(&Value::Inet(IpAddr::from_str("::0").unwrap())), - Err(ValueError::ImpossibleCast.into()) - ); - } - - #[test] - fn try_into_u128() { - let uuid = 195965723427462096757863453463987888808; - assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); - assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); - - let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; - assert_eq!( - u128::try_from(&Value::Str(uuid.to_owned())), - parse_uuid(uuid) - ); - - let ip = Ipv6Addr::from(9876543210); - assert_eq!( - u128::try_from(&Value::Inet(IpAddr::V6(ip))), - Ok(u128::from(ip)) - ); - - assert_eq!( - u128::try_from(&Value::Date(date(2021, 11, 20))), - Err(ValueError::ImpossibleCast.into()) - ); - } - #[test] fn try_into_ipaddr() { macro_rules! test { From eeaff8a15a9545ecba769685427bd80fcc486d79 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 25 Mar 2023 19:12:13 +0900 Subject: [PATCH 43/62] merge main into feature branch --- core/src/data/bigdecimal_ext.rs | 6 ++---- core/src/data/key.rs | 3 +++ core/src/data/value/convert.rs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/data/bigdecimal_ext.rs b/core/src/data/bigdecimal_ext.rs index 955ecd6ad..5028455db 100644 --- a/core/src/data/bigdecimal_ext.rs +++ b/core/src/data/bigdecimal_ext.rs @@ -52,10 +52,8 @@ impl BigDecimalExt for BigDecimal { .then(|| bigdecimal::ToPrimitive::to_u32(self))? } fn to_u64(&self) -> Option { - match self.is_integer() { - true => bigdecimal::ToPrimitive::to_u64(self), - false => None, - } + self.is_integer() + .then(|| bigdecimal::ToPrimitive::to_u64(self))? } fn to_u128(&self) -> Option { self.is_integer() diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 60cc62017..32d88440d 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -128,6 +128,9 @@ impl From for Value { Key::I128(v) => Value::I128(v), Key::U8(v) => Value::U8(v), Key::U16(v) => Value::U16(v), + Key::U32(v) => Value::U32(v), + Key::U64(v) => Value::U64(v), + Key::U128(v) => Value::U128(v), Key::Decimal(v) => Value::Decimal(v), Key::Str(v) => Value::Str(v), Key::Bytea(v) => Value::Bytea(v), diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index b501b680f..875b0b307 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -412,7 +412,7 @@ impl TryFrom<&Value> for u32 { .parse::() .map_err(|_| ValueError::ImpossibleCast)?, Value::Decimal(value) => value.to_u32().ok_or(ValueError::ImpossibleCast)?, - Value::Inet(IpAddr::V4(v)) => Ok(u32::from(*v)), + Value::Inet(IpAddr::V4(value)) => u32::from(*value), Value::Date(_) | Value::Timestamp(_) | Value::Time(_) @@ -482,7 +482,7 @@ impl TryFrom<&Value> for u128 { .map_err(|_| ValueError::ImpossibleCast)?, Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Uuid(value) => *value, - Value::Inet(IpAddr::V6(v)) => Ok(u128::from(*v)), + Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Date(_) | Value::Timestamp(_) | Value::Time(_) From 2c7bcefba015ac4899399f4e8a6ea0ee480a0d5c Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sat, 25 Mar 2023 20:00:47 +0900 Subject: [PATCH 44/62] fix : fix impossibleCast list for the newly added data types --- core/src/data/value/convert.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 875b0b307..67a6475d9 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -418,6 +418,7 @@ impl TryFrom<&Value> for u32 { | Value::Time(_) | Value::Interval(_) | Value::Uuid(_) + | Value::Inet(_) | Value::Map(_) | Value::List(_) | Value::Bytea(_) @@ -452,6 +453,7 @@ impl TryFrom<&Value> for u64 { | Value::Time(_) | Value::Interval(_) | Value::Uuid(_) + | Value::Inet(_) | Value::Map(_) | Value::List(_) | Value::Bytea(_) @@ -481,7 +483,6 @@ impl TryFrom<&Value> for u128 { .parse::() .map_err(|_| ValueError::ImpossibleCast)?, Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, - Value::Uuid(value) => *value, Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Date(_) | Value::Timestamp(_) @@ -489,6 +490,8 @@ impl TryFrom<&Value> for u128 { | Value::Interval(_) | Value::Map(_) | Value::List(_) + | Value::Inet(IpAddr::V4(_)) + | Value::Uuid(_) | Value::Bytea(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) @@ -1460,15 +1463,15 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); - let uuid = 195965723427462096757863453463987888808; - assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); - assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); - - let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; - assert_eq!( - u128::try_from(&Value::Str(uuid.to_owned())), - parse_uuid(uuid) - ); + //let uuid = 195965723427462096757863453463987888808; + //assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); + //assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); + + //let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; + //assert_eq!( + // u128::try_from(&Value::Str(uuid.to_owned())), + // parse_uuid(uuid) + //); let ip = Ipv6Addr::from(9876543210); assert_eq!( From 953bbc2f704450f69c0626ea22b7416d99e8da69 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 26 Mar 2023 00:45:59 +0900 Subject: [PATCH 45/62] fix: handle uuid in u128 format --- core/src/data/value/convert.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 67a6475d9..fbf63e908 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -479,11 +479,13 @@ impl TryFrom<&Value> for u128 { Value::U64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::U128(value) => *value, Value::F64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, - Value::Str(value) => value - .parse::() - .map_err(|_| ValueError::ImpossibleCast)?, + Value::Str(value) => { + value.parse::().map_err(|_| ValueError::ImpossibleCast)?; + parse_uuid(value) + } Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Inet(IpAddr::V6(v)) => u128::from(*v), + Value::Uuid(value) => *value, Value::Date(_) | Value::Timestamp(_) | Value::Time(_) @@ -491,7 +493,6 @@ impl TryFrom<&Value> for u128 { | Value::Map(_) | Value::List(_) | Value::Inet(IpAddr::V4(_)) - | Value::Uuid(_) | Value::Bytea(_) | Value::Null => return Err(ValueError::ImpossibleCast.into()), }) @@ -1463,8 +1464,8 @@ mod tests { Err(ValueError::ImpossibleCast.into()) ); test!(Value::Null, Err(ValueError::ImpossibleCast.into())); - //let uuid = 195965723427462096757863453463987888808; - //assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); + let uuid = 195965723427462096757863453463987888808; + assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); //assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); //let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; From 8748bbc7c3d6b97394e266579dd9fc11c4569b03 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 26 Mar 2023 00:50:48 +0900 Subject: [PATCH 46/62] fix : allow both uuid and str to be ran --- core/src/data/value/convert.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index fbf63e908..6444f839b 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -481,7 +481,7 @@ impl TryFrom<&Value> for u128 { Value::F64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => { value.parse::().map_err(|_| ValueError::ImpossibleCast)?; - parse_uuid(value) + parse_uuid(value).unwrap() } Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Inet(IpAddr::V6(v)) => u128::from(*v), @@ -1466,13 +1466,13 @@ mod tests { test!(Value::Null, Err(ValueError::ImpossibleCast.into())); let uuid = 195965723427462096757863453463987888808; assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); - //assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); + assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); - //let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; - //assert_eq!( - // u128::try_from(&Value::Str(uuid.to_owned())), - // parse_uuid(uuid) - //); + let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; + assert_eq!( + u128::try_from(&Value::Str(uuid.to_owned())), + parse_uuid(uuid) + ); let ip = Ipv6Addr::from(9876543210); assert_eq!( From 1de3bbd8dc1b05df62287c053a005b8d19df4fcf Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 26 Mar 2023 01:08:59 +0900 Subject: [PATCH 47/62] fix: tried using and_then --- core/src/data/value/convert.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 6444f839b..754fa091e 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -479,10 +479,10 @@ impl TryFrom<&Value> for u128 { Value::U64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::U128(value) => *value, Value::F64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, - Value::Str(value) => { - value.parse::().map_err(|_| ValueError::ImpossibleCast)?; - parse_uuid(value).unwrap() - } + Value::Str(value) => value + .parse::() + .map_err(|_| ValueError::ImpossibleCast) + .and_then(|_| Ok(parse_uuid(value))), Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Uuid(value) => *value, From 9731a11bc6bf8d59f758b3ec336b63613d92d466 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 26 Mar 2023 01:24:06 +0900 Subject: [PATCH 48/62] fix: check parse_uuid whether it returns error --- core/src/data/value/convert.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 754fa091e..5331d9371 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -482,7 +482,11 @@ impl TryFrom<&Value> for u128 { Value::Str(value) => value .parse::() .map_err(|_| ValueError::ImpossibleCast) - .and_then(|_| Ok(parse_uuid(value))), + .and_then(|_| { + parse_uuid(value) + .map_err(|_| ValueError::FailedToParseUUID) + .and_then(|uuid| Ok(uuid)) + }), Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Uuid(value) => *value, @@ -1470,8 +1474,8 @@ mod tests { let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; assert_eq!( - u128::try_from(&Value::Str(uuid.to_owned())), - parse_uuid(uuid) + u128::try_from(&Value::Str(uuid.to_owned())), + parse_uuid(uuid) ); let ip = Ipv6Addr::from(9876543210); From 129a5de74d3f0fb5d747e449c24037dcf6f4c237 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 26 Mar 2023 22:01:01 +0900 Subject: [PATCH 49/62] fix: remove unnecessary Str to Uuid conversion --- core/src/data/value/convert.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 5331d9371..1ee8922c3 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -1,7 +1,6 @@ use { super::{ date::{parse_date, parse_time, parse_timestamp}, - uuid::parse_uuid, Value, ValueError, }, crate::{ @@ -481,12 +480,7 @@ impl TryFrom<&Value> for u128 { Value::F64(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Str(value) => value .parse::() - .map_err(|_| ValueError::ImpossibleCast) - .and_then(|_| { - parse_uuid(value) - .map_err(|_| ValueError::FailedToParseUUID) - .and_then(|uuid| Ok(uuid)) - }), + .map_err(|_| ValueError::ImpossibleCast)?, Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Uuid(value) => *value, @@ -701,7 +695,7 @@ impl TryFrom<&Value> for IpAddr { #[cfg(test)] mod tests { use { - super::{parse_uuid, Value, ValueError}, + super::{ Value, ValueError}, crate::{data::Interval as I, result::Result}, chrono::{self, NaiveDate, NaiveDateTime, NaiveTime}, rust_decimal::Decimal, @@ -1472,12 +1466,6 @@ mod tests { assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); - let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; - assert_eq!( - u128::try_from(&Value::Str(uuid.to_owned())), - parse_uuid(uuid) - ); - let ip = Ipv6Addr::from(9876543210); assert_eq!( u128::try_from(&Value::Inet(IpAddr::V6(ip))), From 96e19f1af81287dcb2f2dc4dc274c761fb3a8639 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 2 Apr 2023 11:37:36 +0900 Subject: [PATCH 50/62] fix: fix wierdly merged lines --- core/src/data/key.rs | 8 +++++++- core/src/data/value/convert.rs | 5 ----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 40219baa2..73563deb9 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -59,6 +59,9 @@ impl Ord for Key { (Key::I128(l), Key::I128(r)) => l.cmp(r), (Key::U8(l), Key::U8(r)) => l.cmp(r), (Key::U16(l), Key::U16(r)) => l.cmp(r), + (Key::U32(l),Key::U32(r))=> l.cmp(r), + (Key::U64(l),Key::U64(r))=> l.cmp(r), + (Key::U128(l),Key::U128(r))=> l.cmp(r), (Key::F64(l), Key::F64(r)) => l.total_cmp(&r.0), (Key::Decimal(l), Key::Decimal(r)) => l.cmp(r), (Key::Bool(l), Key::Bool(r)) => l.cmp(r), @@ -81,6 +84,9 @@ impl Ord for Key { | (Key::I128(_), _) | (Key::U8(_), _) | (Key::U16(_), _) + | (Key::U32(_),_) + | (Key::U64(_),_) + | (Key::U128(_),_) | (Key::F64(_), _) | (Key::Decimal(_), _) | (Key::Bool(_), _) @@ -119,7 +125,7 @@ impl PartialOrd for Key { (Key::Interval(l), Key::Interval(r)) => l.partial_cmp(r), (Key::Uuid(l), Key::Uuid(r)) => Some(l.cmp(r)), _ => None, - } + }; Some(self.cmp(other)) } } diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index e7dd234e4..8492bf446 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -697,11 +697,6 @@ mod tests { use { super::{ Value, ValueError}, crate::{data::Interval as I, result::Result}, - super::{Value, ValueError}, - crate::{ - data::{value::uuid::parse_uuid, Interval as I}, - result::Result, - }, chrono::{self, NaiveDate, NaiveDateTime, NaiveTime}, rust_decimal::Decimal, std::{ From b8c1cb3846d6e0fd0b44044ebb5e0daf606f5a9c Mon Sep 17 00:00:00 2001 From: chobobdev Date: Sun, 2 Apr 2023 15:10:18 +0900 Subject: [PATCH 51/62] test : add test case --- core/src/data/value/convert.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 8492bf446..7a021310f 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -481,7 +481,7 @@ impl TryFrom<&Value> for u128 { Value::Str(value) => value .parse::() .map_err(|_| ValueError::ImpossibleCast)?, - Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, + Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Uuid(value) => *value, Value::Date(_) @@ -1467,12 +1467,24 @@ mod tests { assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); + let num = "340282366920938463463374607431768211455"; + assert_eq!( + u128::try_from(&Value::Str(num.to_owned())), + Ok(340282366920938463463374607431768211455) + ); + let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; + assert_eq!( + u128::try_from(&Value::Str(uuid.to_owned())), + Err(ValueError::FailedToParseNumber.into()) + ); let ip = Ipv6Addr::from(9876543210); assert_eq!( u128::try_from(&Value::Inet(IpAddr::V6(ip))), Ok(u128::from(ip)) ); + + assert_eq!( u128::try_from(&Value::Date(date(2021, 11, 20))), Err(ValueError::ImpossibleCast.into()) From 9c62b2c75cd744ba9bcb621eec2ea47ac945f23d Mon Sep 17 00:00:00 2001 From: chobobdev Date: Tue, 4 Apr 2023 20:40:29 +0900 Subject: [PATCH 52/62] fix : reset the branch --- core/src/data/value/convert.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 7a021310f..8492bf446 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -481,7 +481,7 @@ impl TryFrom<&Value> for u128 { Value::Str(value) => value .parse::() .map_err(|_| ValueError::ImpossibleCast)?, - + Value::Decimal(value) => value.to_u128().ok_or(ValueError::ImpossibleCast)?, Value::Inet(IpAddr::V6(v)) => u128::from(*v), Value::Uuid(value) => *value, Value::Date(_) @@ -1467,24 +1467,12 @@ mod tests { assert_eq!((&Value::Uuid(uuid)).try_into() as Result, Ok(uuid)); assert_eq!(u128::try_from(&Value::Uuid(uuid)), Ok(uuid)); - let num = "340282366920938463463374607431768211455"; - assert_eq!( - u128::try_from(&Value::Str(num.to_owned())), - Ok(340282366920938463463374607431768211455) - ); - let uuid = "936DA01F9ABD4d9d80C702AF85C822A8"; - assert_eq!( - u128::try_from(&Value::Str(uuid.to_owned())), - Err(ValueError::FailedToParseNumber.into()) - ); let ip = Ipv6Addr::from(9876543210); assert_eq!( u128::try_from(&Value::Inet(IpAddr::V6(ip))), Ok(u128::from(ip)) ); - - assert_eq!( u128::try_from(&Value::Date(date(2021, 11, 20))), Err(ValueError::ImpossibleCast.into()) From bdbff0d67c8371d8d283acdd73dbbffd2ffc6d72 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Tue, 4 Apr 2023 20:49:12 +0900 Subject: [PATCH 53/62] style: cargo fmt --- core/src/data/key.rs | 12 ++++++------ core/src/data/value/convert.rs | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 73563deb9..cc7d40ff0 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -59,9 +59,9 @@ impl Ord for Key { (Key::I128(l), Key::I128(r)) => l.cmp(r), (Key::U8(l), Key::U8(r)) => l.cmp(r), (Key::U16(l), Key::U16(r)) => l.cmp(r), - (Key::U32(l),Key::U32(r))=> l.cmp(r), - (Key::U64(l),Key::U64(r))=> l.cmp(r), - (Key::U128(l),Key::U128(r))=> l.cmp(r), + (Key::U32(l), Key::U32(r)) => l.cmp(r), + (Key::U64(l), Key::U64(r)) => l.cmp(r), + (Key::U128(l), Key::U128(r)) => l.cmp(r), (Key::F64(l), Key::F64(r)) => l.total_cmp(&r.0), (Key::Decimal(l), Key::Decimal(r)) => l.cmp(r), (Key::Bool(l), Key::Bool(r)) => l.cmp(r), @@ -84,9 +84,9 @@ impl Ord for Key { | (Key::I128(_), _) | (Key::U8(_), _) | (Key::U16(_), _) - | (Key::U32(_),_) - | (Key::U64(_),_) - | (Key::U128(_),_) + | (Key::U32(_), _) + | (Key::U64(_), _) + | (Key::U128(_), _) | (Key::F64(_), _) | (Key::Decimal(_), _) | (Key::Bool(_), _) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 8cf95afcf..6a7e5cd0d 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -680,7 +680,6 @@ impl TryFrom<&Value> for Interval { } } - impl TryFrom<&Value> for IpAddr { type Error = Error; From b63e68ddde58183047ef9e8bf265eddf5d15ddb7 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 16:53:02 +0900 Subject: [PATCH 54/62] fix: remove unnecessary comment --- core/src/data/value/literal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/data/value/literal.rs b/core/src/data/value/literal.rs index 1ee75dec7..1d257cf06 100644 --- a/core/src/data/value/literal.rs +++ b/core/src/data/value/literal.rs @@ -551,7 +551,7 @@ mod tests { assert_eq!(Value::Bool(true), Literal::Boolean(true)); assert_eq!(Value::I8(8), num!("8")); - assert_eq!(Value::I32(32), num!("32")); // should this work? + assert_eq!(Value::I32(32), num!("32")); assert_eq!(Value::I16(16), num!("16")); assert_eq!(Value::I32(32), num!("32")); assert_eq!(Value::I64(64), num!("64")); From dbb509492fdd2e7b1be39d1453075a3546cbdedd Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 17:00:28 +0900 Subject: [PATCH 55/62] test : improve line coverage --- core/src/data/key.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index cc7d40ff0..099b2c0b0 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -492,6 +492,15 @@ mod tests { assert!(Key::U16(10) > Key::U16(3)); assert!(Key::U16(1) > Key::Decimal(dec("1"))); + + assert!(Key::U32(10) > Key::U16(3)); + assert!(Key::U32(1) > Key::Decimal(dec("1"))); + + assert!(Key::U64(10) > Key::U64(3)); + assert!(Key::U64(1) > Key::Decimal(dec("1"))); + + assert!(Key::U128(10) > Key::U128(3)); + assert!(Key::U128(1) > Key::Decimal(dec("1"))); assert!(Key::Decimal(dec("123.45")) > Key::Decimal(dec("0.11"))); assert!(Key::Decimal(dec("1")) > Key::Bool(true)); @@ -812,6 +821,9 @@ mod tests { assert_eq!(Value::from(Key::I128(32)), Value::I128(32)); assert_eq!(Value::from(Key::U8(64)), Value::U8(64)); assert_eq!(Value::from(Key::U16(128)), Value::U16(128)); + assert_eq!(Value::from(Key::U32(128)), Value::U32(128)); + assert_eq!(Value::from(Key::U64(128)), Value::U64(128)); + assert_eq!(Value::from(Key::U128(128)), Value::U128(128)); assert_eq!(Value::from(Key::F64(1.0.into())), Value::F64(1.0)); assert_eq!( Value::from(Key::Decimal(Decimal::from_str("123.45").unwrap())), From 59357a74fb3ec35d7d5891aedda2fd8458d9d2dd Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 17:04:08 +0900 Subject: [PATCH 56/62] style: cargo fmt --- core/src/data/key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 099b2c0b0..9a0a16722 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -492,7 +492,7 @@ mod tests { assert!(Key::U16(10) > Key::U16(3)); assert!(Key::U16(1) > Key::Decimal(dec("1"))); - + assert!(Key::U32(10) > Key::U16(3)); assert!(Key::U32(1) > Key::Decimal(dec("1"))); From da3be0db90970efba32773d9a5fd7c907abe2283 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 17:11:09 +0900 Subject: [PATCH 57/62] test : fix misleading test case --- core/src/data/key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/data/key.rs b/core/src/data/key.rs index 9a0a16722..0796301e5 100644 --- a/core/src/data/key.rs +++ b/core/src/data/key.rs @@ -493,7 +493,7 @@ mod tests { assert!(Key::U16(10) > Key::U16(3)); assert!(Key::U16(1) > Key::Decimal(dec("1"))); - assert!(Key::U32(10) > Key::U16(3)); + assert!(Key::U32(10) > Key::U32(3)); assert!(Key::U32(1) > Key::Decimal(dec("1"))); assert!(Key::U64(10) > Key::U64(3)); From b32e786dbcaf89ead45f4d7c1ac980c0308be80b Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 17:34:02 +0900 Subject: [PATCH 58/62] test: add ommited test case --- core/src/data/value/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index 37a40f3f3..c85f51221 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1287,6 +1287,14 @@ mod tests { test!(multiply U64(3), U8(2) => U64(6)); test!(multiply U64(3), F64(2.0) => F64(6.0)); + test!(multiply U128(3), I8(2) => U128(6)); + test!(multiply U128(3), I16(2) => U128(6)); + test!(multiply U128(3), I32(2) => U128(6)); + test!(multiply U128(3), I64(2) => U128(6)); + test!(multiply U128(3), I128(2) => U128(6)); + test!(multiply U128(3), U8(2) => U128(6)); + test!(multiply U128(3), F64(2.0) => F64(6.0)); + test!(multiply F64(3.0), F64(2.0) => F64(6.0)); test!(multiply F64(3.0), I8(2) => F64(6.0)); test!(multiply F64(3.0), I32(2) => F64(6.0)); From d8feb371c2037091b3659f5a1c849841f880441a Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 17:53:36 +0900 Subject: [PATCH 59/62] test : improve line coverage --- core/src/data/value/convert.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/data/value/convert.rs b/core/src/data/value/convert.rs index 6a7e5cd0d..e5a6c2f2c 100644 --- a/core/src/data/value/convert.rs +++ b/core/src/data/value/convert.rs @@ -840,6 +840,11 @@ mod tests { test!(Value::I32(3), Err(ValueError::ImpossibleCast.into())); test!(Value::I64(3), Err(ValueError::ImpossibleCast.into())); test!(Value::I128(3), Err(ValueError::ImpossibleCast.into())); + test!(Value::U8(3), Err(ValueError::ImpossibleCast.into())); + test!(Value::U16(3), Err(ValueError::ImpossibleCast.into())); + test!(Value::U32(3), Err(ValueError::ImpossibleCast.into())); + test!(Value::U64(3), Err(ValueError::ImpossibleCast.into())); + test!(Value::U128(3), Err(ValueError::ImpossibleCast.into())); test!( Value::Inet(IpAddr::from_str("::1").unwrap()), Err(ValueError::ImpossibleCast.into()) @@ -1560,6 +1565,9 @@ mod tests { test!(Value::I128(122), Ok(122)); test!(Value::U8(122), Ok(122)); test!(Value::U16(122), Ok(122)); + test!(Value::U32(122), Ok(122)); + test!(Value::U64(122), Ok(122)); + test!(Value::U128(122), Ok(122)); test!(Value::I64(1234567890), Ok(1234567890)); test!(Value::F64(1234567890.0), Ok(1234567890)); test!(Value::F64(1234567890.1), Ok(1234567890)); From 9693a128a5d167b8124a0f1fceadd29ba5680286 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 22:43:18 +0900 Subject: [PATCH 60/62] test : add ommitted test case --- core/src/data/value/mod.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/core/src/data/value/mod.rs b/core/src/data/value/mod.rs index c85f51221..b68106a12 100644 --- a/core/src/data/value/mod.rs +++ b/core/src/data/value/mod.rs @@ -1717,6 +1717,30 @@ mod tests { cast!(Str("11".to_owned()) => Uint8, U8(11)); cast!(Null => Uint8, Null); + cast!(Bool(true) => Uint16, U16(1)); + cast!(Bool(false) => Uint16, U16(0)); + cast!(F64(1.1) => Uint16, U16(1)); + cast!(Str("11".to_owned()) => Uint16, U16(11)); + cast!(Null => Uint16, Null); + + cast!(Bool(true) => Uint32, U32(1)); + cast!(Bool(false) => Uint32, U32(0)); + cast!(F64(1.1) => Uint32, U32(1)); + cast!(Str("11".to_owned()) => Uint32, U32(11)); + cast!(Null => Uint32, Null); + + cast!(Bool(true) => Uint64, U64(1)); + cast!(Bool(false) => Uint64, U64(0)); + cast!(F64(1.1) => Uint64, U64(1)); + cast!(Str("11".to_owned()) => Uint64, U64(11)); + cast!(Null => Uint64, Null); + + cast!(Bool(true) => Uint128, U128(1)); + cast!(Bool(false) => Uint128, U128(0)); + cast!(F64(1.1) => Uint128, U128(1)); + cast!(Str("11".to_owned()) => Uint128, U128(11)); + cast!(Null => Uint128, Null); + // Float cast!(Bool(true) => Float, F64(1.0)); cast!(Bool(false) => Float, F64(0.0)); @@ -1727,6 +1751,9 @@ mod tests { cast!(I128(1) => Float, F64(1.0)); cast!(U8(1) => Float, F64(1.0)); cast!(U16(1) => Float, F64(1.0)); + cast!(U32(1) => Float, F64(1.0)); + cast!(U64(1) => Float, F64(1.0)); + cast!(U128(1) => Float, F64(1.0)); cast!(Str("11".to_owned()) => Float, F64(11.0)); cast!(Null => Float, Null); @@ -1740,6 +1767,9 @@ mod tests { cast!(I128(11) => Text, Str("11".to_owned())); cast!(U8(11) => Text, Str("11".to_owned())); cast!(U16(11) => Text, Str("11".to_owned())); + cast!(U32(11) => Text, Str("11".to_owned())); + cast!(U64(11) => Text, Str("11".to_owned())); + cast!(U128(11) => Text, Str("11".to_owned())); cast!(F64(1.0) => Text, Str("1".to_owned())); cast!(inet("::1") => Text, Str("::1".to_owned())); @@ -1813,6 +1843,9 @@ mod tests { assert_eq!(Str("A".to_owned()).concat(I128(1)), Str("A1".to_owned())); assert_eq!(Str("A".to_owned()).concat(U8(1)), Str("A1".to_owned())); assert_eq!(Str("A".to_owned()).concat(U16(1)), Str("A1".to_owned())); + assert_eq!(Str("A".to_owned()).concat(U32(1)), Str("A1".to_owned())); + assert_eq!(Str("A".to_owned()).concat(U64(1)), Str("A1".to_owned())); + assert_eq!(Str("A".to_owned()).concat(U128(1)), Str("A1".to_owned())); assert_eq!(Str("A".to_owned()).concat(F64(1.0)), Str("A1".to_owned())); assert_eq!( List(vec![I64(1)]).concat(List(vec![I64(2)])), From 3bfc0682d86149d0a02eb819308a5b2f09e009c8 Mon Sep 17 00:00:00 2001 From: chobobdev Date: Thu, 6 Apr 2023 23:03:55 +0900 Subject: [PATCH 61/62] test : change misleading test case --- core/src/data/value/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/data/value/expr.rs b/core/src/data/value/expr.rs index 0e7f53c6e..ad8190c76 100644 --- a/core/src/data/value/expr.rs +++ b/core/src/data/value/expr.rs @@ -196,7 +196,7 @@ mod tests { assert_eq!( Value::U128(128).try_into(), Ok(Expr::Literal(AstLiteral::Number( - BigDecimal::from_u32(128).unwrap() + BigDecimal::from_u128(128).unwrap() ))) ); From 6a4252ac9bbdf6eb69bff0169f3fa454197b600a Mon Sep 17 00:00:00 2001 From: chobobdev Date: Fri, 7 Apr 2023 00:07:09 +0900 Subject: [PATCH 62/62] test : add omitted test-suites for new data types --- test-suite/src/data_type/uint128.rs | 47 +++++++++++++++++++++++++++++ test-suite/src/data_type/uint32.rs | 47 +++++++++++++++++++++++++++++ test-suite/src/data_type/uint64.rs | 47 +++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 test-suite/src/data_type/uint128.rs create mode 100644 test-suite/src/data_type/uint32.rs create mode 100644 test-suite/src/data_type/uint64.rs diff --git a/test-suite/src/data_type/uint128.rs b/test-suite/src/data_type/uint128.rs new file mode 100644 index 000000000..7c206eb5d --- /dev/null +++ b/test-suite/src/data_type/uint128.rs @@ -0,0 +1,47 @@ +use { + crate::*, + gluesql_core::{data::ValueError, prelude::Value::*}, +}; + +test_case!(uint128, async move { + run!( + "CREATE TABLE Item ( + field_one UINT128, + field_two UINT128, + );" + ); + run!(r#"INSERT INTO Item VALUES (1, 1), (2, 2), (3, 3), (4, 4);"#); + + test!( + "INSERT INTO Item VALUES (327689,327689);", + Err(ValueError::FailedToParseNumber.into()) + ); + + test!( + "INSERT INTO Item VALUES (-32769, -32769);", + Err(ValueError::FailedToParseNumber.into()) + ); + test!( + "SELECT field_one, field_two FROM Item", + Ok(select!( + field_one | field_two + U128 | U128; + 1 1; + 2 2; + 3 3; + 4 4 + )) + ); + test!( + "SELECT field_one FROM Item WHERE field_one > 0", + Ok(select!(field_one U128; 1; 2;3;4)) + ); + test!( + "SELECT field_one FROM Item WHERE field_one >= 0", + Ok(select!(field_one U128; 1; 2;3;4)) + ); + test!( + "SELECT field_one FROM Item WHERE field_one = 2", + Ok(select!(field_one U128; 2)) + ); +}); diff --git a/test-suite/src/data_type/uint32.rs b/test-suite/src/data_type/uint32.rs new file mode 100644 index 000000000..56a713488 --- /dev/null +++ b/test-suite/src/data_type/uint32.rs @@ -0,0 +1,47 @@ +use { + crate::*, + gluesql_core::{data::ValueError, prelude::Value::*}, +}; + +test_case!(uint32, async move { + run!( + "CREATE TABLE Item ( + field_one UINT32, + field_two UINT32, + );" + ); + run!(r#"INSERT INTO Item VALUES (1, 1), (2, 2), (3, 3), (4, 4);"#); + + test!( + "INSERT INTO Item VALUES (327689,327689);", + Err(ValueError::FailedToParseNumber.into()) + ); + + test!( + "INSERT INTO Item VALUES (-32769, -32769);", + Err(ValueError::FailedToParseNumber.into()) + ); + test!( + "SELECT field_one, field_two FROM Item", + Ok(select!( + field_one | field_two + U32 | U32; + 1 1; + 2 2; + 3 3; + 4 4 + )) + ); + test!( + "SELECT field_one FROM Item WHERE field_one > 0", + Ok(select!(field_one U32; 1; 2;3;4)) + ); + test!( + "SELECT field_one FROM Item WHERE field_one >= 0", + Ok(select!(field_one U32; 1; 2;3;4)) + ); + test!( + "SELECT field_one FROM Item WHERE field_one = 2", + Ok(select!(field_one U32; 2)) + ); +}); diff --git a/test-suite/src/data_type/uint64.rs b/test-suite/src/data_type/uint64.rs new file mode 100644 index 000000000..a69398785 --- /dev/null +++ b/test-suite/src/data_type/uint64.rs @@ -0,0 +1,47 @@ +use { + crate::*, + gluesql_core::{data::ValueError, prelude::Value::*}, +}; + +test_case!(uint64, async move { + run!( + "CREATE TABLE Item ( + field_one UINT64, + field_two UINT64, + );" + ); + run!(r#"INSERT INTO Item VALUES (1, 1), (2, 2), (3, 3), (4, 4);"#); + + test!( + "INSERT INTO Item VALUES (327689,327689);", + Err(ValueError::FailedToParseNumber.into()) + ); + + test!( + "INSERT INTO Item VALUES (-32769, -32769);", + Err(ValueError::FailedToParseNumber.into()) + ); + test!( + "SELECT field_one, field_two FROM Item", + Ok(select!( + field_one | field_two + U64 | U64; + 1 1; + 2 2; + 3 3; + 4 4 + )) + ); + test!( + "SELECT field_one FROM Item WHERE field_one > 0", + Ok(select!(field_one U64; 1; 2;3;4)) + ); + test!( + "SELECT field_one FROM Item WHERE field_one >= 0", + Ok(select!(field_one U64; 1; 2;3;4)) + ); + test!( + "SELECT field_one FROM Item WHERE field_one = 2", + Ok(select!(field_one U64; 2)) + ); +});