From 79126fb63163e6e985f8b79bbc42d8b0324d6f47 Mon Sep 17 00:00:00 2001 From: Denis Smirnov <sd@picodata.io> Date: Wed, 10 Aug 2022 16:32:54 +0700 Subject: [PATCH] feat: implement "not in" operator --- src/executor/tests.rs | 3 ++ src/executor/tests/not_in.rs | 69 +++++++++++++++++++++++++ src/frontend/sql.rs | 3 +- src/frontend/sql/ast.rs | 2 + src/frontend/sql/ir.rs | 1 + src/frontend/sql/query.pest | 17 +++--- src/ir/operator.rs | 4 ++ src/ir/transformation/redistribution.rs | 9 ++-- 8 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 src/executor/tests/not_in.rs diff --git a/src/executor/tests.rs b/src/executor/tests.rs index 7d18caa9ea..ce0e6c9939 100644 --- a/src/executor/tests.rs +++ b/src/executor/tests.rs @@ -1371,3 +1371,6 @@ fn get_motion_policy(plan: &Plan, motion_id: usize) -> &MotionPolicy { #[cfg(test)] mod between; + +#[cfg(test)] +mod not_in; diff --git a/src/executor/tests/not_in.rs b/src/executor/tests/not_in.rs new file mode 100644 index 0000000000..bdfded6dfc --- /dev/null +++ b/src/executor/tests/not_in.rs @@ -0,0 +1,69 @@ +use pretty_assertions::assert_eq; + +use crate::executor::engine::cartridge::backend::sql::ir::PatternWithParams; +use crate::executor::engine::mock::RouterRuntimeMock; +use crate::executor::result::ProducerResult; +use crate::executor::vtable::VirtualTable; +use crate::ir::relation::{Column, ColumnRole, Type}; +use crate::ir::transformation::redistribution::tests::get_motion_id; +use crate::ir::transformation::redistribution::MotionPolicy; +use crate::ir::value::Value; + +use super::*; + +#[test] +fn not_in1_test() { + let sql = r#" + SELECT "identification_number" FROM "hash_testing" AS "t" + WHERE "identification_number" NOT IN ( + SELECT "identification_number" as "id" FROM "hash_testing_hist" + WHERE "identification_number" = 3 + ) + "#; + + // Initialize the query. + let coordinator = RouterRuntimeMock::new(); + let mut query = Query::new(&coordinator, sql, &[]).unwrap(); + let plan = query.exec_plan.get_ir_plan(); + + // Validate the motion type. + let motion_id = *get_motion_id(&plan, 0, 0).unwrap(); + assert_eq!(&MotionPolicy::Full, get_motion_policy(plan, motion_id)); + assert_eq!(true, get_motion_id(plan, 0, 1).is_none()); + + // Mock a virtual table. + let mut virtual_table = VirtualTable::new(); + virtual_table.add_column(Column { + name: "id".into(), + r#type: Type::Integer, + role: ColumnRole::User, + }); + virtual_table.add_tuple(vec![Value::from(3_u64)]); + query + .coordinator + .add_virtual_table(motion_id, virtual_table); + + // Execute the query. + let result = *query + .dispatch() + .unwrap() + .downcast::<ProducerResult>() + .unwrap(); + + // Validate the result. + let mut expected = ProducerResult::new(); + expected.rows.extend(vec![vec![ + Value::String(format!("Execute query on all buckets")), + Value::String(String::from(PatternWithParams::new( + format!( + "{} {}", + r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#, + r#"WHERE ("t"."identification_number") not in (SELECT COLUMN_1 as "id" FROM (VALUES (?)))"#, + ), + vec![ + Value::from(3_u64), + ], + ))), + ]]); + assert_eq!(expected, result); +} diff --git a/src/frontend/sql.rs b/src/frontend/sql.rs index 8d8c1c049b..3a6a857d33 100644 --- a/src/frontend/sql.rs +++ b/src/frontend/sql.rs @@ -460,7 +460,8 @@ impl Ast for AbstractSyntaxTree { | Type::GtEq | Type::Lt | Type::LtEq - | Type::NotEq => { + | Type::NotEq + | Type::NotIn => { let ast_left_id = node.children.get(0).ok_or_else(|| { QueryPlannerError::CustomError( "Left node id is not found among comparison children.".into(), diff --git a/src/frontend/sql/ast.rs b/src/frontend/sql/ast.rs index 5f8a1729bc..b1a76f3660 100644 --- a/src/frontend/sql/ast.rs +++ b/src/frontend/sql/ast.rs @@ -48,6 +48,7 @@ pub enum Type { LtEq, Name, NotEq, + NotIn, Null, Or, Parameter, @@ -101,6 +102,7 @@ impl Type { Rule::LtEq => Ok(Type::LtEq), Rule::Name => Ok(Type::Name), Rule::NotEq => Ok(Type::NotEq), + Rule::NotIn => Ok(Type::NotIn), Rule::Null => Ok(Type::Null), Rule::Or => Ok(Type::Or), Rule::Parameter => Ok(Type::Parameter), diff --git a/src/frontend/sql/ir.rs b/src/frontend/sql/ir.rs index 39acdff593..f60284307c 100644 --- a/src/frontend/sql/ir.rs +++ b/src/frontend/sql/ir.rs @@ -29,6 +29,7 @@ impl Bool { Type::Lt => Ok(Bool::Lt), Type::LtEq => Ok(Bool::LtEq), Type::NotEq => Ok(Bool::NotEq), + Type::NotIn => Ok(Bool::NotIn), _ => Err(QueryPlannerError::InvalidBool), } } diff --git a/src/frontend/sql/query.pest b/src/frontend/sql/query.pest index f8bf690c45..4692358b17 100644 --- a/src/frontend/sql/query.pest +++ b/src/frontend/sql/query.pest @@ -33,28 +33,31 @@ Expr = _{ Or | And | Unary | Between | Cmp | Primary | Parentheses } Primary = _{ SubQuery | Value | Reference } Unary = _{ IsNull } IsNull = { Primary ~ ^"is" ~ ^"null" } - Cmp = _{ Eq | In | Gt | GtEq | Lt | LtEq | NotEq } + Cmp = _{ Eq | In | Gt | GtEq | Lt | LtEq | NotEq | NotIn } Eq = { EqLeft ~ "=" ~ EqRight } EqLeft = _{ Primary } EqRight = _{ Eq | EqLeft } In = { InLeft ~ ^"in" ~ InRight } - InLeft = _{ EqRight } + InLeft = _{ Primary } InRight = _{ In | InLeft } Gt = { GtLeft ~ ">" ~ GtRight } - GtLeft = _{ InRight } + GtLeft = _{ Primary } GtRight = _{ Gt | GtLeft } GtEq = { GtEqLeft ~ ">=" ~ GtEqRight } - GtEqLeft = _{ GtRight } + GtEqLeft = _{ Primary } GtEqRight = _{ GtEq | GtEqLeft } Lt = { LtLeft ~ "<" ~ LtRight } - LtLeft = _{ GtEqRight } + LtLeft = _{ Primary } LtRight = _{ Lt | LtLeft } LtEq = { LtEqLeft ~ "<=" ~ LtEqRight } - LtEqLeft = _{ LtRight } + LtEqLeft = _{ Primary } LtEqRight = _{ LtEq | LtEqLeft } NotEq = { NotEqLeft ~ ("<>" | "!=") ~ NotEqRight } - NotEqLeft = _{ LtEqRight } + NotEqLeft = _{ Primary } NotEqRight = _{ NotEq | NotEqLeft } + NotIn = { NotInLeft ~ ^"not" ~ ^"in" ~ NotInRight } + NotInLeft = _{ Primary } + NotInRight = _{ NotIn | NotInLeft } Between = { BetweenLeft ~ ^"between" ~ BetweenCenter ~ ^"and" ~ BetweenRight } BetweenLeft = _{ Primary } BetweenCenter = _{ Primary } diff --git a/src/ir/operator.rs b/src/ir/operator.rs index fcf5610239..2860407d89 100644 --- a/src/ir/operator.rs +++ b/src/ir/operator.rs @@ -37,6 +37,8 @@ pub enum Bool { LtEq, /// `!=` NotEq, + /// `not in` + NotIn, /// `||` Or, } @@ -57,6 +59,7 @@ impl Bool { "<" => Ok(Bool::Lt), "<=" => Ok(Bool::LtEq), "!=" | "<>" => Ok(Bool::NotEq), + "not in" => Ok(Bool::NotIn), _ => Err(QueryPlannerError::InvalidBool), } } @@ -74,6 +77,7 @@ impl Display for Bool { Bool::Lt => "<", Bool::LtEq => "<=", Bool::NotEq => "<>", + Bool::NotIn => "not in", }; write!(f, "{}", op) diff --git a/src/ir/transformation/redistribution.rs b/src/ir/transformation/redistribution.rs index 603990898c..f4d2bf08d6 100644 --- a/src/ir/transformation/redistribution.rs +++ b/src/ir/transformation/redistribution.rs @@ -718,9 +718,12 @@ impl Plan { Bool::Eq | Bool::In => { self.join_policy_for_eq(rel_id, bool_op.left, bool_op.right)? } - Bool::NotEq | Bool::Gt | Bool::GtEq | Bool::Lt | Bool::LtEq => { - MotionPolicy::Full - } + Bool::NotEq + | Bool::NotIn + | Bool::Gt + | Bool::GtEq + | Bool::Lt + | Bool::LtEq => MotionPolicy::Full, Bool::And | Bool::Or => { // "a and 1" or "a or 1" expressions make no sense. return Err(QueryPlannerError::CustomError( -- GitLab