fix: support unary operator in default value, partition rule and prepare statement (#4301)

* handle unary operator

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* add sqlness test

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* add prepare test

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* add test and context

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* fix rebase error

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* fix merge error

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

* fix sqlness

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>

---------

Signed-off-by: Ruihang Xia <waynestxia@gmail.com>
Co-authored-by: dennis zhuang <killme2008@gmail.com>
This commit is contained in:
Ruihang Xia
2024-07-09 16:59:06 +08:00
committed by GitHub
parent 7fe3f496ac
commit 185953e586
21 changed files with 422 additions and 35 deletions

View File

@@ -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.

View File

@@ -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)]

View File

@@ -192,6 +192,10 @@ impl DateTime {
pub fn to_date(&self) -> Option<Date> {
self.to_chrono_datetime().map(|d| Date::from(d.date()))
}
pub fn negative(&self) -> Self {
Self(-self.0)
}
}
#[cfg(test)]

View File

@@ -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.

View File

@@ -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<i128> for Interval {

View File

@@ -145,6 +145,11 @@ impl Time {
None
}
}
pub fn negative(mut self) -> Self {
self.value = -self.value;
self
}
}
impl From<i64> for Time {

View File

@@ -441,6 +441,11 @@ impl Timestamp {
ParseTimestampSnafu { raw: s }.fail()
}
pub fn negative(mut self) -> Self {
self.value = -self.value;
self
}
}
impl Timestamp {

View File

@@ -367,6 +367,56 @@ impl Value {
Ok(scalar_value)
}
/// Apply `-` unary op if possible
pub fn try_negative(&self) -> Option<Self> {
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<T: LogicalPrimitiveType> {

View File

@@ -13,6 +13,7 @@
// limitations under the License.
#![feature(assert_matches)]
#![feature(if_let_guard)]
pub mod delete;
pub mod error;

View File

@@ -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)?
};

View File

@@ -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<UnaryOperator>,
) -> Result<Value> {
sql_value_to_value("<NONAME>", &data_type, value, Some(timezone)).context(ParseSqlValueSnafu)
sql_value_to_value("<NONAME>", &data_type, value, Some(timezone), unary_op)
.context(ParseSqlValueSnafu)
}
/// Merge table level table options with schema level table options.

View File

@@ -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;

View File

@@ -205,7 +205,19 @@ pub fn convert_value(param: &ParamValue, t: &ConcreteDataType) -> Result<ScalarV
pub fn convert_expr_to_scalar_value(param: &Expr, t: &ConcreteDataType) -> Result<ScalarValue> {
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)

View File

@@ -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,
}
}

View File

@@ -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),
}

View File

@@ -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<UnaryOperator>,
) -> Result<Value> {
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());
}
}

View File

@@ -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<Utc> = 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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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`;