From 185953e58624c05523c8e751a90c8eeb27bcb29c Mon Sep 17 00:00:00 2001 From: Ruihang Xia Date: Tue, 9 Jul 2024 16:59:06 +0800 Subject: [PATCH] fix: support unary operator in default value, partition rule and prepare statement (#4301) * handle unary operator Signed-off-by: Ruihang Xia * add sqlness test Signed-off-by: Ruihang Xia * add prepare test Signed-off-by: Ruihang Xia * add test and context Signed-off-by: Ruihang Xia * fix rebase error Signed-off-by: Ruihang Xia * fix merge error Signed-off-by: Ruihang Xia * fix sqlness Signed-off-by: Ruihang Xia --------- Signed-off-by: Ruihang Xia Co-authored-by: dennis zhuang --- src/common/decimal/src/decimal128.rs | 5 + src/common/time/src/date.rs | 4 + src/common/time/src/datetime.rs | 4 + src/common/time/src/duration.rs | 5 + src/common/time/src/interval.rs | 9 + src/common/time/src/time.rs | 5 + src/common/time/src/timestamp.rs | 5 + src/datatypes/src/value.rs | 50 +++++ src/operator/src/lib.rs | 1 + .../src/req_convert/insert/stmt_to_region.rs | 2 +- src/operator/src/statement/ddl.rs | 26 ++- src/servers/src/lib.rs | 1 + src/servers/src/mysql/helper.rs | 14 +- src/sql/src/error.rs | 20 +- src/sql/src/parsers/create_parser.rs | 4 + src/sql/src/statements.rs | 195 +++++++++++++++--- tests-integration/tests/sql.rs | 9 +- .../common/insert/insert_default.result | 25 +++ .../common/insert/insert_default.sql | 13 ++ .../cases/standalone/common/partition.result | 37 ++++ tests/cases/standalone/common/partition.sql | 23 +++ 21 files changed, 422 insertions(+), 35 deletions(-) diff --git a/src/common/decimal/src/decimal128.rs b/src/common/decimal/src/decimal128.rs index d742be5876f4..fc4331ddfcc0 100644 --- a/src/common/decimal/src/decimal128.rs +++ b/src/common/decimal/src/decimal128.rs @@ -121,6 +121,11 @@ impl Decimal128 { let value = (hi | lo) as i128; Self::new(value, precision, scale) } + + pub fn negative(mut self) -> Self { + self.value = -self.value; + self + } } /// The default value of Decimal128 is 0, and its precision is 1 and scale is 0. diff --git a/src/common/time/src/date.rs b/src/common/time/src/date.rs index a04b529fd448..86759d737d41 100644 --- a/src/common/time/src/date.rs +++ b/src/common/time/src/date.rs @@ -159,6 +159,10 @@ impl Date { .checked_sub_days(Days::new(days as u64)) .map(Into::into) } + + pub fn negative(&self) -> Self { + Self(-self.0) + } } #[cfg(test)] diff --git a/src/common/time/src/datetime.rs b/src/common/time/src/datetime.rs index f1980a38d1af..4a60470aebce 100644 --- a/src/common/time/src/datetime.rs +++ b/src/common/time/src/datetime.rs @@ -192,6 +192,10 @@ impl DateTime { pub fn to_date(&self) -> Option { self.to_chrono_datetime().map(|d| Date::from(d.date())) } + + pub fn negative(&self) -> Self { + Self(-self.0) + } } #[cfg(test)] diff --git a/src/common/time/src/duration.rs b/src/common/time/src/duration.rs index 06742ba294b5..d39596b47297 100644 --- a/src/common/time/src/duration.rs +++ b/src/common/time/src/duration.rs @@ -92,6 +92,11 @@ impl Duration { pub fn to_std_duration(self) -> std::time::Duration { self.into() } + + pub fn negative(mut self) -> Self { + self.value = -self.value; + self + } } /// Convert i64 to Duration Type. diff --git a/src/common/time/src/interval.rs b/src/common/time/src/interval.rs index 95e8982f071e..cd57028d29f6 100644 --- a/src/common/time/src/interval.rs +++ b/src/common/time/src/interval.rs @@ -281,6 +281,15 @@ impl Interval { pub fn to_i32(&self) -> i32 { self.months } + + pub fn negative(&self) -> Self { + Self { + months: -self.months, + days: -self.days, + nsecs: -self.nsecs, + unit: self.unit, + } + } } impl From for Interval { diff --git a/src/common/time/src/time.rs b/src/common/time/src/time.rs index 8490195ff601..f8ffbf3608b1 100644 --- a/src/common/time/src/time.rs +++ b/src/common/time/src/time.rs @@ -145,6 +145,11 @@ impl Time { None } } + + pub fn negative(mut self) -> Self { + self.value = -self.value; + self + } } impl From for Time { diff --git a/src/common/time/src/timestamp.rs b/src/common/time/src/timestamp.rs index a4aac7fd54ba..503c44cf9901 100644 --- a/src/common/time/src/timestamp.rs +++ b/src/common/time/src/timestamp.rs @@ -441,6 +441,11 @@ impl Timestamp { ParseTimestampSnafu { raw: s }.fail() } + + pub fn negative(mut self) -> Self { + self.value = -self.value; + self + } } impl Timestamp { diff --git a/src/datatypes/src/value.rs b/src/datatypes/src/value.rs index 4e9db7e2e52d..fdb6b38bb698 100644 --- a/src/datatypes/src/value.rs +++ b/src/datatypes/src/value.rs @@ -367,6 +367,56 @@ impl Value { Ok(scalar_value) } + + /// Apply `-` unary op if possible + pub fn try_negative(&self) -> Option { + match self { + Value::Null => Some(Value::Null), + Value::UInt8(x) => { + if *x == 0 { + Some(Value::UInt8(*x)) + } else { + None + } + } + Value::UInt16(x) => { + if *x == 0 { + Some(Value::UInt16(*x)) + } else { + None + } + } + Value::UInt32(x) => { + if *x == 0 { + Some(Value::UInt32(*x)) + } else { + None + } + } + Value::UInt64(x) => { + if *x == 0 { + Some(Value::UInt64(*x)) + } else { + None + } + } + Value::Int8(x) => Some(Value::Int8(-*x)), + Value::Int16(x) => Some(Value::Int16(-*x)), + Value::Int32(x) => Some(Value::Int32(-*x)), + Value::Int64(x) => Some(Value::Int64(-*x)), + Value::Float32(x) => Some(Value::Float32(-*x)), + Value::Float64(x) => Some(Value::Float64(-*x)), + Value::Decimal128(x) => Some(Value::Decimal128(x.negative())), + Value::Date(x) => Some(Value::Date(x.negative())), + Value::DateTime(x) => Some(Value::DateTime(x.negative())), + Value::Timestamp(x) => Some(Value::Timestamp(x.negative())), + Value::Time(x) => Some(Value::Time(x.negative())), + Value::Duration(x) => Some(Value::Duration(x.negative())), + Value::Interval(x) => Some(Value::Interval(x.negative())), + + Value::Binary(_) | Value::String(_) | Value::Boolean(_) | Value::List(_) => None, + } + } } pub trait TryAsPrimitive { diff --git a/src/operator/src/lib.rs b/src/operator/src/lib.rs index efced830f22b..b3e7e3afb9d8 100644 --- a/src/operator/src/lib.rs +++ b/src/operator/src/lib.rs @@ -13,6 +13,7 @@ // limitations under the License. #![feature(assert_matches)] +#![feature(if_let_guard)] pub mod delete; pub mod error; diff --git a/src/operator/src/req_convert/insert/stmt_to_region.rs b/src/operator/src/req_convert/insert/stmt_to_region.rs index 71f107020d62..37d55e6c9e90 100644 --- a/src/operator/src/req_convert/insert/stmt_to_region.rs +++ b/src/operator/src/req_convert/insert/stmt_to_region.rs @@ -202,7 +202,7 @@ fn sql_value_to_grpc_value( column: column.clone(), })? } else { - statements::sql_value_to_value(column, &column_schema.data_type, sql_val, timezone) + statements::sql_value_to_value(column, &column_schema.data_type, sql_val, timezone, None) .context(ParseSqlSnafu)? }; diff --git a/src/operator/src/statement/ddl.rs b/src/operator/src/statement/ddl.rs index daef8b74579b..0f1775a0924b 100644 --- a/src/operator/src/statement/ddl.rs +++ b/src/operator/src/statement/ddl.rs @@ -56,7 +56,7 @@ use sql::statements::create::{ }; use sql::statements::sql_value_to_value; use sql::statements::statement::Statement; -use sqlparser::ast::{Expr, Ident, Value as ParserValue}; +use sqlparser::ast::{Expr, Ident, UnaryOperator, Value as ParserValue}; use store_api::metric_engine_consts::{LOGICAL_TABLE_METADATA_KEY, METRIC_ENGINE_NAME}; use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan}; use table::dist_table::DistTable; @@ -1329,14 +1329,30 @@ fn convert_one_expr( // convert leaf node. let (lhs, op, rhs) = match (left.as_ref(), right.as_ref()) { + // col, val (Expr::Identifier(ident), Expr::Value(value)) => { let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?; - let value = convert_value(value, data_type, timezone)?; + let value = convert_value(value, data_type, timezone, None)?; (Operand::Column(column_name), op, Operand::Value(value)) } + (Expr::Identifier(ident), Expr::UnaryOp { op: unary_op, expr }) + if let Expr::Value(v) = &**expr => + { + let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?; + let value = convert_value(v, data_type, timezone, Some(*unary_op))?; + (Operand::Column(column_name), op, Operand::Value(value)) + } + // val, col (Expr::Value(value), Expr::Identifier(ident)) => { let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?; - let value = convert_value(value, data_type, timezone)?; + let value = convert_value(value, data_type, timezone, None)?; + (Operand::Value(value), op, Operand::Column(column_name)) + } + (Expr::UnaryOp { op: unary_op, expr }, Expr::Identifier(ident)) + if let Expr::Value(v) = &**expr => + { + let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?; + let value = convert_value(v, data_type, timezone, Some(*unary_op))?; (Operand::Value(value), op, Operand::Column(column_name)) } (Expr::BinaryOp { .. }, Expr::BinaryOp { .. }) => { @@ -1372,8 +1388,10 @@ fn convert_value( value: &ParserValue, data_type: ConcreteDataType, timezone: &Timezone, + unary_op: Option, ) -> Result { - sql_value_to_value("", &data_type, value, Some(timezone)).context(ParseSqlValueSnafu) + sql_value_to_value("", &data_type, value, Some(timezone), unary_op) + .context(ParseSqlValueSnafu) } /// Merge table level table options with schema level table options. diff --git a/src/servers/src/lib.rs b/src/servers/src/lib.rs index 85ebea4ca8a3..a8f97877bdda 100644 --- a/src/servers/src/lib.rs +++ b/src/servers/src/lib.rs @@ -16,6 +16,7 @@ #![feature(try_blocks)] #![feature(exclusive_wrapper)] #![feature(let_chains)] +#![feature(if_let_guard)] use datatypes::schema::Schema; use query::plan::LogicalPlan; diff --git a/src/servers/src/mysql/helper.rs b/src/servers/src/mysql/helper.rs index df174b38400c..e18053a9b793 100644 --- a/src/servers/src/mysql/helper.rs +++ b/src/servers/src/mysql/helper.rs @@ -205,7 +205,19 @@ pub fn convert_value(param: &ParamValue, t: &ConcreteDataType) -> Result Result { match param { Expr::Value(v) => { - let v = sql_value_to_value("", t, v, None); + let v = sql_value_to_value("", t, v, None, None); + match v { + Ok(v) => v + .try_to_scalar_value(t) + .context(error::ConvertScalarValueSnafu), + Err(e) => error::InvalidParameterSnafu { + reason: e.to_string(), + } + .fail(), + } + } + Expr::UnaryOp { op, expr } if let Expr::Value(v) = &**expr => { + let v = sql_value_to_value("", t, v, None, Some(*op)); match v { Ok(v) => v .try_to_scalar_value(t) diff --git a/src/sql/src/error.rs b/src/sql/src/error.rs index 18453f8b3099..19ff7f47be7c 100644 --- a/src/sql/src/error.rs +++ b/src/sql/src/error.rs @@ -20,6 +20,7 @@ use common_macro::stack_trace_debug; use common_time::timestamp::TimeUnit; use common_time::Timestamp; use datafusion_common::DataFusionError; +use datafusion_sql::sqlparser::ast::UnaryOperator; use datatypes::prelude::{ConcreteDataType, Value}; use snafu::{Location, Snafu}; use sqlparser::ast::Ident; @@ -161,6 +162,21 @@ pub enum Error { source: datatypes::error::Error, }, + #[snafu(display("Invalid unary operator {} for value {}", unary_op, value))] + InvalidUnaryOp { + unary_op: UnaryOperator, + value: Value, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Unsupported unary operator {}", unary_op))] + UnsupportedUnaryOp { + unary_op: UnaryOperator, + #[snafu(implicit)] + location: Location, + }, + #[snafu(display("Unrecognized table option key: {}", key))] InvalidTableOption { key: String, @@ -299,7 +315,8 @@ impl ErrorExt for Error { | ConvertToLogicalExpression { .. } | Simplification { .. } | InvalidInterval { .. } - | PermissionDenied { .. } + | InvalidUnaryOp { .. } + | UnsupportedUnaryOp { .. } | FulltextInvalidOption { .. } => StatusCode::InvalidArguments, SerializeColumnDefaultConstraint { source, .. } => source.status_code(), @@ -307,6 +324,7 @@ impl ErrorExt for Error { ConvertToDfStatement { .. } => StatusCode::Internal, ConvertSqlValue { .. } | ConvertValue { .. } => StatusCode::Unsupported, + PermissionDenied { .. } => StatusCode::PermissionDenied, SetFulltextOption { .. } => StatusCode::Unexpected, } } diff --git a/src/sql/src/parsers/create_parser.rs b/src/sql/src/parsers/create_parser.rs index da315123a26a..565b621a649b 100644 --- a/src/sql/src/parsers/create_parser.rs +++ b/src/sql/src/parsers/create_parser.rs @@ -940,6 +940,10 @@ fn ensure_one_expr(expr: &Expr, columns: &[&Column]) -> Result<()> { Ok(()) } Expr::Value(_) => Ok(()), + Expr::UnaryOp { expr, .. } => { + ensure_one_expr(expr, columns)?; + Ok(()) + } _ => error::InvalidSqlSnafu { msg: format!("Partition rule expr {:?} is not a binary expr!", expr), } diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index ccf93861d809..196e3b9c9863 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -52,9 +52,9 @@ use crate::ast::{ }; use crate::error::{ self, ColumnTypeMismatchSnafu, ConvertSqlValueSnafu, ConvertToGrpcDataTypeSnafu, - ConvertValueSnafu, InvalidCastSnafu, InvalidSqlValueSnafu, ParseSqlValueSnafu, Result, - SerializeColumnDefaultConstraintSnafu, SetFulltextOptionSnafu, TimestampOverflowSnafu, - UnsupportedDefaultValueSnafu, + ConvertValueSnafu, InvalidCastSnafu, InvalidSqlValueSnafu, InvalidUnaryOpSnafu, + ParseSqlValueSnafu, Result, SerializeColumnDefaultConstraintSnafu, SetFulltextOptionSnafu, + TimestampOverflowSnafu, UnsupportedDefaultValueSnafu, UnsupportedUnaryOpSnafu, }; use crate::statements::create::Column; pub use crate::statements::option_map::OptionMap; @@ -229,8 +229,9 @@ pub fn sql_value_to_value( data_type: &ConcreteDataType, sql_val: &SqlValue, timezone: Option<&Timezone>, + unary_op: Option, ) -> Result { - let value = match sql_val { + let mut value = match sql_val { SqlValue::Number(n, _) => sql_number_to_value(data_type, n)?, SqlValue::Null => Value::Null, SqlValue::Boolean(b) => { @@ -260,6 +261,60 @@ pub fn sql_value_to_value( .fail() } }; + + if let Some(unary_op) = unary_op { + match unary_op { + UnaryOperator::Plus | UnaryOperator::Minus | UnaryOperator::Not => {} + UnaryOperator::PGBitwiseNot + | UnaryOperator::PGSquareRoot + | UnaryOperator::PGCubeRoot + | UnaryOperator::PGPostfixFactorial + | UnaryOperator::PGPrefixFactorial + | UnaryOperator::PGAbs => { + return UnsupportedUnaryOpSnafu { unary_op }.fail(); + } + } + + match value { + Value::Null => {} + Value::Boolean(bool) => match unary_op { + UnaryOperator::Not => value = Value::Boolean(!bool), + _ => { + return InvalidUnaryOpSnafu { unary_op, value }.fail(); + } + }, + Value::UInt8(_) + | Value::UInt16(_) + | Value::UInt32(_) + | Value::UInt64(_) + | Value::Int8(_) + | Value::Int16(_) + | Value::Int32(_) + | Value::Int64(_) + | Value::Float32(_) + | Value::Float64(_) + | Value::Decimal128(_) + | Value::Date(_) + | Value::DateTime(_) + | Value::Timestamp(_) + | Value::Time(_) + | Value::Duration(_) + | Value::Interval(_) => match unary_op { + UnaryOperator::Plus => {} + UnaryOperator::Minus => { + value = value + .try_negative() + .with_context(|| InvalidUnaryOpSnafu { unary_op, value })?; + } + _ => return InvalidUnaryOpSnafu { unary_op, value }.fail(), + }, + + Value::String(_) | Value::Binary(_) | Value::List(_) => { + return InvalidUnaryOpSnafu { unary_op, value }.fail() + } + } + } + if value.data_type() != *data_type { cast(value, data_type).with_context(|_| InvalidCastSnafu { sql_value: sql_val.clone(), @@ -305,7 +360,7 @@ fn parse_column_default_constraint( { let default_constraint = match &opt.option { ColumnOption::Default(Expr::Value(v)) => ColumnDefaultConstraint::Value( - sql_value_to_value(column_name, data_type, v, timezone)?, + sql_value_to_value(column_name, data_type, v, timezone, None)?, ), ColumnOption::Default(Expr::Function(func)) => { let mut func = format!("{func}").to_lowercase(); @@ -316,20 +371,22 @@ fn parse_column_default_constraint( // Always use lowercase for function expression ColumnDefaultConstraint::Function(func.to_lowercase()) } - ColumnOption::Default(expr) => { - if let Expr::UnaryOp { op, expr } = expr { - if let (UnaryOperator::Minus, Expr::Value(SqlValue::Number(n, _))) = - (op, expr.as_ref()) - { - return Ok(Some(ColumnDefaultConstraint::Value(sql_number_to_value( - data_type, - &format!("-{n}"), - )?))); + ColumnOption::Default(Expr::UnaryOp { op, expr }) => { + if let Expr::Value(v) = &**expr { + let value = sql_value_to_value(column_name, data_type, v, timezone, Some(*op))?; + ColumnDefaultConstraint::Value(value) + } else { + return UnsupportedDefaultValueSnafu { + column_name, + expr: *expr.clone(), } + .fail(); } + } + ColumnOption::Default(others) => { return UnsupportedDefaultValueSnafu { column_name, - expr: expr.clone(), + expr: others.clone(), } .fail(); } @@ -689,28 +746,61 @@ mod tests { let sql_val = SqlValue::Null; assert_eq!( Value::Null, - sql_value_to_value("a", &ConcreteDataType::float64_datatype(), &sql_val, None).unwrap() + sql_value_to_value( + "a", + &ConcreteDataType::float64_datatype(), + &sql_val, + None, + None + ) + .unwrap() ); let sql_val = SqlValue::Boolean(true); assert_eq!( Value::Boolean(true), - sql_value_to_value("a", &ConcreteDataType::boolean_datatype(), &sql_val, None).unwrap() + sql_value_to_value( + "a", + &ConcreteDataType::boolean_datatype(), + &sql_val, + None, + None + ) + .unwrap() ); let sql_val = SqlValue::Number("3.0".to_string(), false); assert_eq!( Value::Float64(OrderedFloat(3.0)), - sql_value_to_value("a", &ConcreteDataType::float64_datatype(), &sql_val, None).unwrap() + sql_value_to_value( + "a", + &ConcreteDataType::float64_datatype(), + &sql_val, + None, + None + ) + .unwrap() ); let sql_val = SqlValue::Number("3.0".to_string(), false); - let v = sql_value_to_value("a", &ConcreteDataType::boolean_datatype(), &sql_val, None); + let v = sql_value_to_value( + "a", + &ConcreteDataType::boolean_datatype(), + &sql_val, + None, + None, + ); assert!(v.is_err()); assert!(format!("{v:?}").contains("Failed to parse number '3.0' to boolean column type")); let sql_val = SqlValue::Boolean(true); - let v = sql_value_to_value("a", &ConcreteDataType::float64_datatype(), &sql_val, None); + let v = sql_value_to_value( + "a", + &ConcreteDataType::float64_datatype(), + &sql_val, + None, + None, + ); assert!(v.is_err()); assert!( format!("{v:?}").contains( @@ -720,20 +810,38 @@ mod tests { ); let sql_val = SqlValue::HexStringLiteral("48656c6c6f20776f726c6421".to_string()); - let v = - sql_value_to_value("a", &ConcreteDataType::binary_datatype(), &sql_val, None).unwrap(); + let v = sql_value_to_value( + "a", + &ConcreteDataType::binary_datatype(), + &sql_val, + None, + None, + ) + .unwrap(); assert_eq!(Value::Binary(Bytes::from(b"Hello world!".as_slice())), v); let sql_val = SqlValue::DoubleQuotedString("MorningMyFriends".to_string()); - let v = - sql_value_to_value("a", &ConcreteDataType::binary_datatype(), &sql_val, None).unwrap(); + let v = sql_value_to_value( + "a", + &ConcreteDataType::binary_datatype(), + &sql_val, + None, + None, + ) + .unwrap(); assert_eq!( Value::Binary(Bytes::from(b"MorningMyFriends".as_slice())), v ); let sql_val = SqlValue::HexStringLiteral("9AF".to_string()); - let v = sql_value_to_value("a", &ConcreteDataType::binary_datatype(), &sql_val, None); + let v = sql_value_to_value( + "a", + &ConcreteDataType::binary_datatype(), + &sql_val, + None, + None, + ); assert!(v.is_err()); assert!( format!("{v:?}").contains("odd number of digits"), @@ -741,7 +849,13 @@ mod tests { ); let sql_val = SqlValue::HexStringLiteral("AG".to_string()); - let v = sql_value_to_value("a", &ConcreteDataType::binary_datatype(), &sql_val, None); + let v = sql_value_to_value( + "a", + &ConcreteDataType::binary_datatype(), + &sql_val, + None, + None, + ); assert!(v.is_err()); assert!(format!("{v:?}").contains("invalid character"), "v is {v:?}",); } @@ -753,6 +867,7 @@ mod tests { &ConcreteDataType::date_datatype(), &SqlValue::DoubleQuotedString("2022-02-22".to_string()), None, + None, ) .unwrap(); assert_eq!(ConcreteDataType::date_datatype(), value.data_type()); @@ -768,6 +883,7 @@ mod tests { &ConcreteDataType::date_datatype(), &SqlValue::DoubleQuotedString("2022-02-22".to_string()), Some(&Timezone::from_tz_string("+07:00").unwrap()), + None, ) .unwrap(); assert_eq!(ConcreteDataType::date_datatype(), value.data_type()); @@ -786,6 +902,7 @@ mod tests { &ConcreteDataType::datetime_datatype(), &SqlValue::DoubleQuotedString("2022-02-22 00:01:03+0800".to_string()), None, + None, ) .unwrap(); assert_eq!(ConcreteDataType::datetime_datatype(), value.data_type()); @@ -803,6 +920,7 @@ mod tests { &ConcreteDataType::datetime_datatype(), &SqlValue::DoubleQuotedString("2022-02-22 00:01:61".to_string()), None, + None ) .is_err()); } @@ -1247,7 +1365,32 @@ mod tests { &ConcreteDataType::string_datatype(), &SqlValue::Placeholder("default".into()), None, + None ) .is_err()); + assert!(sql_value_to_value( + "test", + &ConcreteDataType::string_datatype(), + &SqlValue::Placeholder("default".into()), + None, + Some(UnaryOperator::Minus), + ) + .is_err()); + assert!(sql_value_to_value( + "test", + &ConcreteDataType::uint16_datatype(), + &SqlValue::Number("3".into(), false), + None, + Some(UnaryOperator::Minus), + ) + .is_err()); + assert!(sql_value_to_value( + "test", + &ConcreteDataType::uint16_datatype(), + &SqlValue::Number("3".into(), false), + None, + None + ) + .is_ok()); } } diff --git a/tests-integration/tests/sql.rs b/tests-integration/tests/sql.rs index ba3d847b644d..29835f904ad1 100644 --- a/tests-integration/tests/sql.rs +++ b/tests-integration/tests/sql.rs @@ -240,18 +240,23 @@ pub async fn test_mysql_crud(store_type: StorageType) { .execute(&pool) .await .unwrap(); + sqlx::query("insert into demo(i) values(?)") + .bind(-99) + .execute(&pool) + .await + .unwrap(); let rows = sqlx::query("select * from demo") .fetch_all(&pool) .await .unwrap(); - assert_eq!(rows.len(), 1); + assert_eq!(rows.len(), 2); for row in rows { let i: i64 = row.get("i"); let ts: DateTime = row.get("ts"); let now = common_time::util::current_time_millis(); assert!(now - ts.timestamp_millis() < 1000); - assert_eq!(i, 99); + assert_eq!(i.abs(), 99); } let _ = fe_mysql_server.shutdown().await; diff --git a/tests/cases/standalone/common/insert/insert_default.result b/tests/cases/standalone/common/insert/insert_default.result index da5ac155e1a9..64d6175993e4 100644 --- a/tests/cases/standalone/common/insert/insert_default.result +++ b/tests/cases/standalone/common/insert/insert_default.result @@ -64,3 +64,28 @@ DROP TABLE test2; Affected Rows: 0 +CREATE TABLE test3 ( + i INTEGER DEFAULT -1, + j DOUBLE DEFAULT -2, + k TIMESTAMP DEFAULT -3, + ts TIMESTAMP TIME INDEX, +); + +Affected Rows: 0 + +INSERT INTO test3 (ts) VALUES (1); + +Affected Rows: 1 + +SELECT * FROM test3; + ++----+------+-------------------------+-------------------------+ +| i | j | k | ts | ++----+------+-------------------------+-------------------------+ +| -1 | -2.0 | 1969-12-31T23:59:59.997 | 1970-01-01T00:00:00.001 | ++----+------+-------------------------+-------------------------+ + +DROP TABLE test3; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/insert/insert_default.sql b/tests/cases/standalone/common/insert/insert_default.sql index 4e50fc59c978..5d41b77e5ccf 100644 --- a/tests/cases/standalone/common/insert/insert_default.sql +++ b/tests/cases/standalone/common/insert/insert_default.sql @@ -20,3 +20,16 @@ SELECT * FROM test2; DROP TABLE test1; DROP TABLE test2; + +CREATE TABLE test3 ( + i INTEGER DEFAULT -1, + j DOUBLE DEFAULT -2, + k TIMESTAMP DEFAULT -3, + ts TIMESTAMP TIME INDEX, +); + +INSERT INTO test3 (ts) VALUES (1); + +SELECT * FROM test3; + +DROP TABLE test3; diff --git a/tests/cases/standalone/common/partition.result b/tests/cases/standalone/common/partition.result index 3fb5f50ddb39..cdf0f51be57c 100644 --- a/tests/cases/standalone/common/partition.result +++ b/tests/cases/standalone/common/partition.result @@ -185,3 +185,40 @@ PARTITION ON COLUMNS (a) ( Error: 1004(InvalidArguments), Unclosed value Int32(10) on column a +-- Issue https://github.com/GreptimeTeam/greptimedb/issues/4247 +-- Partition rule with unary operator +CREATE TABLE `molestiAe` ( + `sImiLiQUE` FLOAT NOT NULL, + `amEt` TIMESTAMP(6) TIME INDEX, + `EXpLICaBo` DOUBLE, + PRIMARY KEY (`sImiLiQUE`) +) PARTITION ON COLUMNS (`sImiLiQUE`) ( + `sImiLiQUE` < -1, + `sImiLiQUE` >= -1 AND `sImiLiQUE` < -0, + `sImiLiQUE` >= 0 +); + +Affected Rows: 0 + +INSERT INTO `molestiAe` VALUES + (-2, 0, 0), + (-0.9, 0, 0), + (1, 0, 0); + +Affected Rows: 3 + +-- SQLNESS SORT_RESULT 3 1 +SELECT * FROM `molestiAe`; + ++-----------+---------------------+-----------+ +| sImiLiQUE | amEt | EXpLICaBo | ++-----------+---------------------+-----------+ +| -0.9 | 1970-01-01T00:00:00 | 0.0 | +| -2.0 | 1970-01-01T00:00:00 | 0.0 | +| 1.0 | 1970-01-01T00:00:00 | 0.0 | ++-----------+---------------------+-----------+ + +DROP TABLE `molestiAe`; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/partition.sql b/tests/cases/standalone/common/partition.sql index 55f3d87cf9ab..c65f2c9e97b6 100644 --- a/tests/cases/standalone/common/partition.sql +++ b/tests/cases/standalone/common/partition.sql @@ -85,3 +85,26 @@ PARTITION ON COLUMNS (a) ( a > 10 AND a < 20, a >= 20 ); + +-- Issue https://github.com/GreptimeTeam/greptimedb/issues/4247 +-- Partition rule with unary operator +CREATE TABLE `molestiAe` ( + `sImiLiQUE` FLOAT NOT NULL, + `amEt` TIMESTAMP(6) TIME INDEX, + `EXpLICaBo` DOUBLE, + PRIMARY KEY (`sImiLiQUE`) +) PARTITION ON COLUMNS (`sImiLiQUE`) ( + `sImiLiQUE` < -1, + `sImiLiQUE` >= -1 AND `sImiLiQUE` < -0, + `sImiLiQUE` >= 0 +); + +INSERT INTO `molestiAe` VALUES + (-2, 0, 0), + (-0.9, 0, 0), + (1, 0, 0); + +-- SQLNESS SORT_RESULT 3 1 +SELECT * FROM `molestiAe`; + +DROP TABLE `molestiAe`;