From 44c75b919bd75622790095a0ad4d6d5c6f83dcda Mon Sep 17 00:00:00 2001 From: Dima Koltsov <dkoltsov@picodata.io> Date: Mon, 15 Aug 2022 16:42:50 +0300 Subject: [PATCH] feat: implement "is not null" operator Closes #226 --- src/frontend/sql.rs | 2 +- src/frontend/sql/ast.rs | 2 ++ src/frontend/sql/ir.rs | 1 + src/frontend/sql/ir/tests.rs | 16 +++++++++ src/frontend/sql/query.pest | 5 +-- src/ir/explain.rs | 1 + src/ir/operator.rs | 4 +++ test_app/test/integration/api_test.lua | 48 ++++++++++++++++++++++++++ 8 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/frontend/sql.rs b/src/frontend/sql.rs index 3a6a857d33..2d31b68c01 100644 --- a/src/frontend/sql.rs +++ b/src/frontend/sql.rs @@ -478,7 +478,7 @@ impl Ast for AbstractSyntaxTree { let cond_id = plan.add_cond(plan_left_id, op, plan_right_id)?; map.add(*id, cond_id); } - Type::IsNull => { + Type::IsNull | Type::IsNotNull => { let ast_child_id = node.children.get(0).ok_or_else(|| { QueryPlannerError::CustomError(format!("{:?} has no children.", &node.rule)) })?; diff --git a/src/frontend/sql/ast.rs b/src/frontend/sql/ast.rs index b1a76f3660..7f54017ad8 100644 --- a/src/frontend/sql/ast.rs +++ b/src/frontend/sql/ast.rs @@ -44,6 +44,7 @@ pub enum Type { Insert, Integer, IsNull, + IsNotNull, Lt, LtEq, Name, @@ -98,6 +99,7 @@ impl Type { Rule::Integer => Ok(Type::Integer), Rule::Insert => Ok(Type::Insert), Rule::IsNull => Ok(Type::IsNull), + Rule::IsNotNull => Ok(Type::IsNotNull), Rule::Lt => Ok(Type::Lt), Rule::LtEq => Ok(Type::LtEq), Rule::Name => Ok(Type::Name), diff --git a/src/frontend/sql/ir.rs b/src/frontend/sql/ir.rs index f60284307c..0de79a86d3 100644 --- a/src/frontend/sql/ir.rs +++ b/src/frontend/sql/ir.rs @@ -44,6 +44,7 @@ impl Unary { pub(super) fn from_node_type(s: &Type) -> Result<Self, QueryPlannerError> { match s { Type::IsNull => Ok(Unary::IsNull), + Type::IsNotNull => Ok(Unary::IsNotNull), _ => Err(QueryPlannerError::CustomError(format!( "Invalid unary operator: {:?}", s diff --git a/src/frontend/sql/ir/tests.rs b/src/frontend/sql/ir/tests.rs index 72f6b06322..1cb1932039 100644 --- a/src/frontend/sql/ir/tests.rs +++ b/src/frontend/sql/ir/tests.rs @@ -404,6 +404,22 @@ fn front_sql18() { assert_eq!(sql_to_sql(input, &[], &no_transform), expected); } +#[test] +fn front_sql19() { + let input = r#"SELECT "identification_number" FROM "hash_testing" + WHERE "product_code" IS NOT NULL"#; + let expected = PatternWithParams::new( + format!( + "{} {}", + r#"SELECT "hash_testing"."identification_number""#, + r#"FROM "hash_testing" WHERE ("hash_testing"."product_code") is not null"#, + ), + vec![], + ); + + assert_eq!(sql_to_sql(input, &[], &no_transform), expected); +} + #[test] fn front_params1() { let pattern = r#"SELECT "id", "FIRST_NAME" FROM "test_space" diff --git a/src/frontend/sql/query.pest b/src/frontend/sql/query.pest index 4692358b17..6eb4c9b0d9 100644 --- a/src/frontend/sql/query.pest +++ b/src/frontend/sql/query.pest @@ -31,8 +31,9 @@ Query = _{ Except | UnionAll | Select | Values | Insert } Expr = _{ Or | And | Unary | Between | Cmp | Primary | Parentheses } Parentheses = _{ "(" ~ Expr ~ ")" } Primary = _{ SubQuery | Value | Reference } - Unary = _{ IsNull } - IsNull = { Primary ~ ^"is" ~ ^"null" } + Unary = _{ IsNull | IsNotNull} + IsNull = { Primary ~ ^"is" ~ ^"null" } + IsNotNull = { Primary ~ ^"is" ~ ^"not" ~ ^"null" } Cmp = _{ Eq | In | Gt | GtEq | Lt | LtEq | NotEq | NotIn } Eq = { EqLeft ~ "=" ~ EqRight } EqLeft = _{ Primary } diff --git a/src/ir/explain.rs b/src/ir/explain.rs index a60a9d2e94..0c109a2a20 100644 --- a/src/ir/explain.rs +++ b/src/ir/explain.rs @@ -344,6 +344,7 @@ impl Display for Selection { } Selection::UnaryOp { op, child } => match op { Unary::IsNull => format!("{} {}", child, op), + Unary::IsNotNull => format!("{} {}", child, op), }, }; diff --git a/src/ir/operator.rs b/src/ir/operator.rs index 2860407d89..fec393e2e6 100644 --- a/src/ir/operator.rs +++ b/src/ir/operator.rs @@ -89,6 +89,8 @@ impl Display for Bool { pub enum Unary { /// `is null` IsNull, + /// `is not null` + IsNotNull, } impl Unary { @@ -99,6 +101,7 @@ impl Unary { pub fn from(s: &str) -> Result<Self, QueryPlannerError> { match s.to_lowercase().as_str() { "is null" => Ok(Unary::IsNull), + "is not null" => Ok(Unary::IsNotNull), _ => Err(QueryPlannerError::CustomError(format!( "Invalid unary operator: {}", s @@ -111,6 +114,7 @@ impl Display for Unary { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let op = match &self { Unary::IsNull => "is null", + Unary::IsNotNull => "is not null", }; write!(f, "{}", op) diff --git a/test_app/test/integration/api_test.lua b/test_app/test/integration/api_test.lua index 0a1dc1a062..2ce70b79ec 100644 --- a/test_app/test/integration/api_test.lua +++ b/test_app/test/integration/api_test.lua @@ -8,6 +8,8 @@ g.before_each( function() local api = cluster:server("api-1").net_box + -- "testing_space" contains: + -- [1, "123", 1] local r, err = api:call("sbroad.execute", { [[insert into "testing_space" ("id", "name", "product_units") values (?, ?, ?)]], {1, "123", 1} @@ -15,6 +17,8 @@ g.before_each( t.assert_equals(err, nil) t.assert_equals(r, {row_count = 1}) + -- "testing_space_hist" contains: + -- [1, "123", 5] r, err = api:call("sbroad.execute", { [[insert into "testing_space_hist" ("id", "name", "product_units") values (?, ?, ?)]], {1, "123", 5} @@ -22,6 +26,9 @@ g.before_each( t.assert_equals(err, nil) t.assert_equals(r, {row_count = 1}) + -- "space_simple_shard_key" contains: + -- [1, "ok", 1] + -- [10, NULL, 0] r, err = api:call("sbroad.execute", { [[insert into "space_simple_shard_key" ("id", "name", "sysOp") values (?, ?, ?), (?, ?, ?)]], {1, "ok", 1, 10, box.NULL, 0} @@ -29,6 +36,9 @@ g.before_each( t.assert_equals(err, nil) t.assert_equals(r, {row_count = 2}) + -- "space_simple_shard_key_hist" contains: + -- [1, "ok_hist", 3] + -- [2, "ok_hist_2", 1] r, err = api:call("sbroad.execute", { [[insert into "space_simple_shard_key_hist" ("id", "name", "sysOp") values (?, ?, ?), (?, ?, ?)]], {1, "ok_hist", 3, 2, "ok_hist_2", 1} @@ -36,6 +46,9 @@ g.before_each( t.assert_equals(err, nil) t.assert_equals(r, {row_count = 2}) + -- "t" contains: + -- [1, 4.2] + -- [2, decimal(6.66)] r, err = api:call("sbroad.execute", { [[insert into "t" ("id", "a") values (?, ?), (?, ?)]], {1, 4.2, 2, require('decimal').new(6.66)} @@ -824,6 +837,41 @@ g.test_is_null = function() }) end +g.test_is_not_null_1 = function() + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { [[ + SELECT "id" FROM "space_simple_shard_key" WHERE "name" IS NOT NULL and "id" = 10 + ]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "integer"}, + }, + rows = { + }, + }) +end + +g.test_is_not_null_2 = function() + local api = cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", { [[ + SELECT "id" FROM "space_simple_shard_key" WHERE "name" IS NOT NULL + ]], {} }) + + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "integer"}, + }, + rows = { + {1} + }, + }) +end + g.test_between1 = function() local api = cluster:server("api-1").net_box -- GitLab