From 96bc990a5662ce9181ed3b1884e7d9bbfec184ba Mon Sep 17 00:00:00 2001 From: "ms.evilhat" <ms.evilhat@gmail.com> Date: Wed, 8 Feb 2023 15:19:27 +0300 Subject: [PATCH] feat: support arbitrary expr as a part of projection currently we mean expression with arithmetic, bool and unary operations by arbitrary expr --- .../arbitrary_expressions_test.lua | 156 ++++++++++++++ .../test/integration/explain_test.lua | 60 ++++++ sbroad-core/src/backend/sql/tree/tests.rs | 128 ++++++++++- sbroad-core/src/frontend/sql.rs | 56 +---- sbroad-core/src/frontend/sql/ast.rs | 3 - sbroad-core/src/frontend/sql/ast/tests.rs | 201 +++++++++++++++++- sbroad-core/src/frontend/sql/query.pest | 7 +- sbroad-core/src/ir/explain.rs | 39 +++- sbroad-core/src/ir/expression.rs | 8 +- .../sql/tree/arbitrary_projection_plan.yaml | 187 ++++++++++++++++ .../sql/arbitrary_projection_ast.yaml | 74 +++++++ .../sql/arithmetic_projection_ast.yaml | 12 +- 12 files changed, 853 insertions(+), 78 deletions(-) create mode 100644 sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua create mode 100644 sbroad-core/tests/artifactory/backend/sql/tree/arbitrary_projection_plan.yaml create mode 100644 sbroad-core/tests/artifactory/frontend/sql/arbitrary_projection_ast.yaml diff --git a/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua b/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua new file mode 100644 index 0000000000..8801e19c1b --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua @@ -0,0 +1,156 @@ +local t = require('luatest') +local arbitrary_projection = t.group('arbitrary_projection') + +local helper = require('test.helper.cluster_no_replication') + +local fun = require("fun") + +local cluster = nil + +arbitrary_projection.before_all(function() + helper.start_test_cluster(helper.cluster_config) + cluster = helper.cluster +end) + +arbitrary_projection.before_each( + function() + local api = cluster:server("api-1").net_box + + for i = 1, 10 do + local r, err = api:call("sbroad.execute", { + [[ + insert into "arithmetic_space" + ("id", "a", "b", "c", "d", "e", "f", "boolean_col", "string_col", "number_col") + values (?,?,?,?,?,?,?,?,?,?) + ]], + {i, i, i*2, i*3, -i, -i, -i, true, "123", i}, + }) + t.assert_equals(err, nil) + t.assert_equals(r, {row_count = 1}) + end + end +) + +arbitrary_projection.after_each( + function() + local storage1 = cluster:server("storage-1-1").net_box + storage1:call("box.execute", { [[truncate table "arithmetic_space"]] }) + + local storage2 = cluster:server("storage-2-1").net_box + storage2:call("box.execute", { [[truncate table "arithmetic_space"]] }) + end +) + +arbitrary_projection.after_all(function() + helper.stop_test_cluster() +end) + +arbitrary_projection.test_arbitrary_invalid = function() + local api = cluster:server("api-1").net_box + + local _, err = api:call("sbroad.execute", { [[select "id" + 1 as "alias" > a from "arithmetic_space"]], {} }) + t.assert_str_contains(tostring(err), "rule parsing error") + + local _, err = api:call("sbroad.execute", { [[ + select id" + 1 as "alias" > "a" is not null from "arithmetic_space" + ]], {} }) + t.assert_str_contains(tostring(err), "rule parsing error") + + local _, err = api:call("sbroad.execute", { [[ + select "a" + "b" and true from "arithmetic_space" + ]], {} }) + t.assert_str_contains(tostring(err), "Type mismatch: can not convert integer(3) to boolean") +end + +arbitrary_projection.test_arbitrary_valid = function() + local api = cluster:server("api-1").net_box + + local res_all, err = api:call("sbroad.execute", { [[select "id" from "arithmetic_space"]], {} }) + t.assert_equals(err, nil) + t.assert_not_equals(res_all.rows, {}) + + -- array of {true,true} with lenght equals to rows amount + local all_true = fun.map(function() + return { true, true } + end, res_all.rows):totable() + + -- array of {false,false} with lenght equals to rows amount + local all_false = fun.map(function() + return { false, false } + end, res_all.rows):totable() + + -- projection consisted of arithmetic and bool + local r, err = api:call("sbroad.execute", { [[ + select "id", "id" - 5 > 0, "id" - 5 > 0 as "cmp" from "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_items_equals( + r.metadata, + { + {name = "id", type = "integer"}, + {name = "COLUMN_1", type = "boolean"}, + {name = "cmp", type = "boolean"}, + } + ) + for _, v in pairs(r.rows) do + t.assert_equals(v[2], v[1] - 5 > 0) + t.assert_equals(v[3], v[2]) + end + + local r, err = api:call("sbroad.execute", { [[ + select "id" + "b" > "id" + "b", "id" + "b" > "id" + "b" as "cmp" from "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.rows, all_false) + + local r, err = api:call("sbroad.execute", { [[ + select 0 = "id" + "f", 0 = "id" + "f" as "cmp" from "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.rows, all_true) + + local r, err = api:call("sbroad.execute", { [[ + select 1 > 0, 1 > 0 as "cmp" from "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_items_equals( + r.metadata, + { + { name = "COLUMN_1", type = "boolean" }, + { name = "cmp", type = "boolean" }, + } + ) + t.assert_equals(r.rows, all_true) + + local r, err = api:call("sbroad.execute", { [[ + select + "id" between "id" - 1 and "id" * 4, + "id" between "id" - 1 and "id" * 4 as "between" + from + "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_items_equals( + r.metadata, + { + { name = "COLUMN_1", type = "boolean" }, + { name = "between", type = "boolean" }, + } + ) + t.assert_equals(r.rows, all_true) + + -- projection consisted of arithmetic and unary + local r, err = api:call("sbroad.execute", { [[ + select "id" is not null, "id" is not null as "not_null" from "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.rows, all_true) + + -- projection consisted of arithmetic, bool and cast (function) + local r, err = api:call("sbroad.execute", { [[ + select cast("id" * 2 > 0 as boolean), cast("id" * 2 > 0 as boolean) as "cast" from "arithmetic_space" + ]], {} }) + t.assert_equals(err, nil) + t.assert_equals(r.rows, all_true) + +end \ No newline at end of file diff --git a/sbroad-cartridge/test_app/test/integration/explain_test.lua b/sbroad-cartridge/test_app/test/integration/explain_test.lua index 5e18694e70..9b406d5fcf 100644 --- a/sbroad-cartridge/test_app/test/integration/explain_test.lua +++ b/sbroad-cartridge/test_app/test/integration/explain_test.lua @@ -290,3 +290,63 @@ WHERE "t3"."id" = 2 } ) end + +g.test_explain_arithmetic_projection = function() + local api = cluster:server("api-1").net_box + + -- projection of arithmetic expr with column and constant as operands + local r, err = api:call("sbroad.execute", { + [[EXPLAIN select "id" + 2 from "arithmetic_space"]], {} + }) + + t.assert_equals(err, nil) + t.assert_equals( + r, + { + "projection ((\"arithmetic_space\".\"id\") + (2) -> \"COLUMN_1\")", + " scan \"arithmetic_space\"", + } + ) + + -- projection of arithmetic expr with columns as operands + local r, err = api:call("sbroad.execute", { [[EXPLAIN select "a" + "b" * "c" from "arithmetic_space"]], {} }) + + t.assert_equals(err, nil) + t.assert_equals( + r, + -- luacheck: max line length 140 + { + "projection ((\"arithmetic_space\".\"a\") + (\"arithmetic_space\".\"b\") * (\"arithmetic_space\".\"c\") -> \"COLUMN_1\")", + " scan \"arithmetic_space\"", + } + ) + + -- projection of arithmetic expr with parentheses + local r, err = api:call("sbroad.execute", { [[EXPLAIN select ("a" + "b") * "c" from "arithmetic_space"]], {} }) + + t.assert_equals(err, nil) + t.assert_equals( + r, + -- luacheck: max line length 140 + { + "projection (((\"arithmetic_space\".\"a\") + (\"arithmetic_space\".\"b\")) * (\"arithmetic_space\".\"c\") -> \"COLUMN_1\")", + " scan \"arithmetic_space\"", + } + ) +end + +g.test_explain_arbitrary_projection = function() + local api = cluster:server("api-1").net_box + + -- currently explain does not support projection with bool and unary expressions + + -- arbitraty expression consisted of bool + local _, err = api:call("sbroad.execute", { + [[EXPLAIN select "a" > "b" from "arithmetic_space"]], {} + }) + t.assert_str_contains(tostring(err), "is not supported for yet") + + -- arbitraty expression consisted of unary + local _, err = api:call("sbroad.execute", { [[EXPLAIN select "a" is null from "arithmetic_space"]], {} }) + t.assert_str_contains(tostring(err), "is not supported for yet") +end diff --git a/sbroad-core/src/backend/sql/tree/tests.rs b/sbroad-core/src/backend/sql/tree/tests.rs index 44588c37ad..94dfd082f4 100644 --- a/sbroad-core/src/backend/sql/tree/tests.rs +++ b/sbroad-core/src/backend/sql/tree/tests.rs @@ -4,7 +4,7 @@ use std::path::Path; use pretty_assertions::assert_eq; use crate::backend::sql::tree::{OrderedSyntaxNodes, SyntaxPlan}; -use crate::ir::operator::{Arithmetic, Bool}; +use crate::ir::operator::{Arithmetic, Bool, Unary}; use crate::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type}; use crate::ir::tree::Snapshot; use crate::ir::value::Value; @@ -459,3 +459,129 @@ fn sql_arithmetic_projection_plan() { assert_eq!(None, nodes_iter.next()); } + +#[test] +fn sql_arbitrary_projection_plan() { + // select a + b > c and d is not null from t + let mut plan = Plan::default(); + let t = Table::new_seg( + "t", + vec![ + Column::new("a", Type::Integer, ColumnRole::User), + Column::new("b", Type::Integer, ColumnRole::User), + Column::new("c", Type::Integer, ColumnRole::User), + Column::new("d", Type::Integer, ColumnRole::User), + Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding), + ], + &["a"], + SpaceEngine::Memtx, + ) + .unwrap(); + plan.add_rel(t); + let scan_id = plan.add_scan("t", None).unwrap(); + let a_id = plan.add_row_from_child(scan_id, &["a"]).unwrap(); + let b_id = plan.add_row_from_child(scan_id, &["b"]).unwrap(); + let c_id = plan.add_row_from_child(scan_id, &["c"]).unwrap(); + let d_id = plan.add_row_from_child(scan_id, &["d"]).unwrap(); + + // a + b + let arith_addition_id = plan + .add_arithmetic_to_plan(a_id, Arithmetic::Add, b_id, false) + .unwrap(); + + // a + b > c + let gt_id = plan + .nodes + .add_bool(arith_addition_id, Bool::Gt, c_id) + .unwrap(); + + // d is not null + let unary_id = plan.nodes.add_unary_bool(Unary::IsNotNull, d_id).unwrap(); + + // a + b > c and d is not null + let and_id = plan.nodes.add_bool(gt_id, Bool::And, unary_id).unwrap(); + + let proj_id = plan.add_proj_internal(scan_id, &[and_id]).unwrap(); + plan.set_top(proj_id).unwrap(); + + // check the plan + let path = Path::new("") + .join("tests") + .join("artifactory") + .join("backend") + .join("sql") + .join("tree") + .join("arbitrary_projection_plan.yaml"); + let s = fs::read_to_string(path).unwrap(); + let expected_plan = Plan::from_yaml(&s).unwrap(); + assert_eq!(expected_plan, plan); + + let exec_plan = ExecutionPlan::from(plan.clone()); + let top_id = exec_plan.get_ir_plan().get_top().unwrap(); + + // get nodes in the sql-convenient order + let sp = SyntaxPlan::new(&exec_plan, top_id, Snapshot::Latest).unwrap(); + let ordered = OrderedSyntaxNodes::try_from(sp).unwrap(); + let nodes = ordered.to_syntax_data().unwrap(); + let mut nodes_iter = nodes.into_iter(); + + // projection + assert_eq!(Some(&SyntaxData::PlanId(25)), nodes_iter.next()); + // row 12 + assert_eq!(Some(&SyntaxData::PlanId(13)), nodes_iter.next()); + // ( + assert_eq!(Some(&SyntaxData::OpenParenthesis), nodes_iter.next()); + // ref a + assert_eq!(Some(&SyntaxData::PlanId(12)), nodes_iter.next()); + // ) + assert_eq!(Some(&SyntaxData::CloseParenthesis), nodes_iter.next()); + // arithmetic expression: [a] + [b] + assert_eq!(Some(&SyntaxData::PlanId(20)), nodes_iter.next()); + // arithmetic operator Add (+) + assert_eq!(Some(&SyntaxData::Operator("+".into())), nodes_iter.next()); + // row + assert_eq!(Some(&SyntaxData::PlanId(15)), nodes_iter.next()); + // ( + assert_eq!(Some(&SyntaxData::OpenParenthesis), nodes_iter.next()); + // ref b + assert_eq!(Some(&SyntaxData::PlanId(14)), nodes_iter.next()); + // ) + assert_eq!(Some(&SyntaxData::CloseParenthesis), nodes_iter.next()); + // bool expression gt: [a + b] > [c] + assert_eq!(Some(&SyntaxData::PlanId(21)), nodes_iter.next()); + // bool operator Gt (>) + assert_eq!(Some(&SyntaxData::Operator(">".into())), nodes_iter.next()); + // row + assert_eq!(Some(&SyntaxData::PlanId(17)), nodes_iter.next()); + // ( + assert_eq!(Some(&SyntaxData::OpenParenthesis), nodes_iter.next()); + // ref c + assert_eq!(Some(&SyntaxData::PlanId(16)), nodes_iter.next()); + // ) + assert_eq!(Some(&SyntaxData::CloseParenthesis), nodes_iter.next()); + // bool expression and: [a + b > c] and [d is not null] + assert_eq!(Some(&SyntaxData::PlanId(23)), nodes_iter.next()); + // bool operator And (and) + assert_eq!(Some(&SyntaxData::Operator("and".into())), nodes_iter.next()); + // row + assert_eq!(Some(&SyntaxData::PlanId(19)), nodes_iter.next()); + // ( + assert_eq!(Some(&SyntaxData::OpenParenthesis), nodes_iter.next()); + // ref d + assert_eq!(Some(&SyntaxData::PlanId(18)), nodes_iter.next()); + // ) + assert_eq!(Some(&SyntaxData::CloseParenthesis), nodes_iter.next()); + // unary expression is not null: [d] is not null 19 + assert_eq!(Some(&SyntaxData::PlanId(22)), nodes_iter.next()); + // unary operator IsNotNull (is not null) + assert_eq!( + Some(&SyntaxData::Operator("is not null".into())), + nodes_iter.next() + ); + // from + assert_eq!(Some(&SyntaxData::From), nodes_iter.next()); + // scan + assert_eq!(Some(&SyntaxData::PlanId(11)), nodes_iter.next()); + + assert_eq!(None, nodes_iter.next()); +} diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 9fa9511f35..92f9a12edb 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -140,7 +140,8 @@ impl Ast for AbstractSyntaxTree { map: &Translation, arithmetic_expression_ids: &mut Vec<usize>, rows: &mut HashSet<usize>, - ast_id: usize| { + ast_id: usize| + -> Result<usize, SbroadError> { let plan_id; // if child of current multiplication or addition is `(expr)` then // we need to get expr that is child of `()` and add it to the plan @@ -819,58 +820,6 @@ impl Ast for AbstractSyntaxTree { )); } } - Type::ArithmeticExprAlias => { - // left child is Addition, Multiplication or ArithParentheses - let ast_left_id = ast_column.children.first().ok_or_else(|| { - SbroadError::UnexpectedNumberOfValues( - "ArithmeticExprAlias has no children.".into(), - ) - })?; - - let arithmetic_parse_node = self.nodes.get_node(*ast_left_id)?; - if arithmetic_parse_node.rule != Type::Multiplication - && arithmetic_parse_node.rule != Type::Addition - { - return Err(SbroadError::Invalid( - Entity::Node, - Some(format!("expected Multiplication or Addition as the first child of ArithmeticExprAlias, got {}", - arithmetic_parse_node.rule)), - )); - } - - let cond_id = get_arithmetic_cond_id( - &mut plan, - arithmetic_parse_node, - &map, - &mut arithmetic_expression_ids, - &mut rows, - )?; - - // right child is AliasName if exists - // else means that arithmetic expression does not have an alias - match ast_column.children.get(1).ok_or_else(|| { - SbroadError::NotFound( - Entity::Node, - "that is right node with index 1 among ArithmeticExprAlias children" - .into(), - ) - }) { - Ok(ast_name_id) => { - let name = self - .nodes - .get_node(*ast_name_id)? - .value - .as_ref() - .ok_or_else(|| SbroadError::NotFound(Entity::Name, "of Alias".into()))?; - - let plan_alias_id = plan - .nodes - .add_alias(&normalize_name_from_sql(name), cond_id)?; - columns.push(plan_alias_id); - }, - Err(_) => { columns.push(cond_id); }, - } - } _ => { return Err(SbroadError::Invalid( Entity::Type, @@ -1009,7 +958,6 @@ impl Ast for AbstractSyntaxTree { } Type::AliasName | Type::Add - | Type::ArithmeticExprAlias | Type::ArithParentheses | Type::ColumnName | Type::Divide diff --git a/sbroad-core/src/frontend/sql/ast.rs b/sbroad-core/src/frontend/sql/ast.rs index 00fe9dbd19..3c153f7227 100644 --- a/sbroad-core/src/frontend/sql/ast.rs +++ b/sbroad-core/src/frontend/sql/ast.rs @@ -31,7 +31,6 @@ pub enum Type { AliasName, And, ArithmeticExpr, - ArithmeticExprAlias, ArithParentheses, Asterisk, Between, @@ -114,7 +113,6 @@ impl Type { Rule::AliasName => Ok(Type::AliasName), Rule::And => Ok(Type::And), Rule::ArithmeticExpr => Ok(Type::ArithmeticExpr), - Rule::ArithmeticExprAlias => Ok(Type::ArithmeticExprAlias), Rule::ArithParentheses => Ok(Type::ArithParentheses), Rule::Asterisk => Ok(Type::Asterisk), Rule::Between => Ok(Type::Between), @@ -201,7 +199,6 @@ impl fmt::Display for Type { Type::AliasName => "AliasName".to_string(), Type::And => "And".to_string(), Type::ArithmeticExpr => "ArithmeticExpr".to_string(), - Type::ArithmeticExprAlias => "ArithmeticExprAlias".to_string(), Type::ArithParentheses => "ArithParentheses".to_string(), Type::Asterisk => "Asterisk".to_string(), Type::Between => "Between".to_string(), diff --git a/sbroad-core/src/frontend/sql/ast/tests.rs b/sbroad-core/src/frontend/sql/ast/tests.rs index 663714d953..bf528fd4ba 100644 --- a/sbroad-core/src/frontend/sql/ast/tests.rs +++ b/sbroad-core/src/frontend/sql/ast/tests.rs @@ -356,7 +356,6 @@ fn sql_arithmetic_projection_ast() { assert_eq!(expected_ast, ast); - // note there is no AliasName or Alias type let top = ast.top.unwrap(); let mut dft_post = PostOrder::with_capacity(|node| ast.nodes.ast_iter(node), 64); let mut iter = dft_post.iter(top); @@ -396,9 +395,18 @@ fn sql_arithmetic_projection_ast() { let node = ast.nodes.get_node(addition_id).unwrap(); assert_eq!(node.rule, Type::Addition); - let (_, arithm_id) = iter.next().unwrap(); - let node = ast.nodes.get_node(arithm_id).unwrap(); - assert_eq!(node.rule, Type::ArithmeticExprAlias); + let (_, alias_name_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_name_id).unwrap(); + assert_eq!(node.rule, Type::AliasName); + assert_eq!(node.value, Some(String::from("COLUMN_1"))); + + let (_, alias_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_id).unwrap(); + assert_eq!(node.rule, Type::Alias); + + let (_, col_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_id).unwrap(); + assert_eq!(node.rule, Type::Column); let (_, proj_id) = iter.next().unwrap(); let node = ast.nodes.get_node(proj_id).unwrap(); @@ -468,9 +476,188 @@ fn sql_arithmetic_projection_alias_ast() { assert_eq!(node.rule, Type::AliasName); assert_eq!(node.value, Some("sum".into())); - let (_, arithm_id) = iter.next().unwrap(); - let node = ast.nodes.get_node(arithm_id).unwrap(); - assert_eq!(node.rule, Type::ArithmeticExprAlias); + let (_, alias_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_id).unwrap(); + assert_eq!(node.rule, Type::Alias); + + let (_, col_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_id).unwrap(); + assert_eq!(node.rule, Type::Column); + + let (_, proj_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(proj_id).unwrap(); + assert_eq!(node.rule, Type::Projection); + + assert_eq!(None, iter.next()); +} + +#[test] +fn sql_arbitrary_projection_ast() { + let ast = AbstractSyntaxTree::new("select a + b > c from t").unwrap(); + + let path = Path::new("") + .join("tests") + .join("artifactory") + .join("frontend") + .join("sql") + .join("arbitrary_projection_ast.yaml"); + + let s = fs::read_to_string(path).unwrap(); + let expected_ast = AbstractSyntaxTree::from_yaml(&s).unwrap(); + + assert_eq!(expected_ast, ast); + + let top = ast.top.unwrap(); + let mut dft_post = PostOrder::with_capacity(|node| ast.nodes.ast_iter(node), 64); + let mut iter = dft_post.iter(top); + + let (_, table_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(table_id).unwrap(); + assert_eq!(node.rule, Type::Table); + assert_eq!(node.value, Some("t".to_string())); + + let (_, scan_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(scan_id).unwrap(); + assert_eq!(node.rule, Type::Scan); + + let (_, sel_name_a_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(sel_name_a_id).unwrap(); + assert_eq!(node.rule, Type::ColumnName); + assert_eq!(node.value, Some("a".to_string())); + + let (_, a_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(a_id).unwrap(); + assert_eq!(node.rule, Type::Reference); + + let (_, add_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(add_id).unwrap(); + assert_eq!(node.rule, Type::Add); + + let (_, sel_name_b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(sel_name_b_id).unwrap(); + assert_eq!(node.rule, Type::ColumnName); + assert_eq!(node.value, Some("b".to_string())); + + let (_, b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(b_id).unwrap(); + assert_eq!(node.rule, Type::Reference); + + let (_, addition_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(addition_id).unwrap(); + assert_eq!(node.rule, Type::Addition); + + let (_, col_name_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_name_id).unwrap(); + assert_eq!(node.rule, Type::ColumnName); + + let (_, b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(b_id).unwrap(); + assert_eq!(node.rule, Type::Reference); + + let (_, b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(b_id).unwrap(); + assert_eq!(node.rule, Type::Gt); + + let (_, alias_name_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_name_id).unwrap(); + assert_eq!(node.rule, Type::AliasName); + assert_eq!(node.value, Some(String::from("COLUMN_1"))); + + let (_, alias_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_id).unwrap(); + assert_eq!(node.rule, Type::Alias); + + let (_, col_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_id).unwrap(); + assert_eq!(node.rule, Type::Column); + + let (_, proj_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(proj_id).unwrap(); + assert_eq!(node.rule, Type::Projection); + + assert_eq!(None, iter.next()); +} + +#[test] +fn sql_arbitrary_projection_alias_ast() { + let ast = AbstractSyntaxTree::new("select a as alias1 + b as alias2 from t").unwrap_err(); + assert_eq!( + format!( + r#"rule parsing error: --> 1:10 + | +1 | select a as alias1 + b as alias2 from t + | ^--- + | + = expected Multiply, Divide, Add, or Subtract"#, + ), + format!("{ast}"), + ); + + let ast = AbstractSyntaxTree::new("select a + b > c as gt from t").unwrap(); + + let top = ast.top.unwrap(); + let mut dft_post = PostOrder::with_capacity(|node| ast.nodes.ast_iter(node), 64); + let mut iter = dft_post.iter(top); + + let (_, table_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(table_id).unwrap(); + assert_eq!(node.rule, Type::Table); + assert_eq!(node.value, Some("t".to_string())); + + let (_, scan_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(scan_id).unwrap(); + assert_eq!(node.rule, Type::Scan); + + let (_, sel_name_a_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(sel_name_a_id).unwrap(); + assert_eq!(node.rule, Type::ColumnName); + assert_eq!(node.value, Some("a".to_string())); + + let (_, a_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(a_id).unwrap(); + assert_eq!(node.rule, Type::Reference); + + let (_, add_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(add_id).unwrap(); + assert_eq!(node.rule, Type::Add); + + let (_, sel_name_b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(sel_name_b_id).unwrap(); + assert_eq!(node.rule, Type::ColumnName); + assert_eq!(node.value, Some("b".to_string())); + + let (_, b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(b_id).unwrap(); + assert_eq!(node.rule, Type::Reference); + + let (_, col_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_id).unwrap(); + assert_eq!(node.rule, Type::Addition); + + let (_, col_name_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_name_id).unwrap(); + assert_eq!(node.rule, Type::ColumnName); + + let (_, b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(b_id).unwrap(); + assert_eq!(node.rule, Type::Reference); + + let (_, b_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(b_id).unwrap(); + assert_eq!(node.rule, Type::Gt); + + let (_, alias_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_id).unwrap(); + assert_eq!(node.rule, Type::AliasName); + assert_eq!(node.value, Some("gt".into())); + + let (_, alias_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(alias_id).unwrap(); + assert_eq!(node.rule, Type::Alias); + + let (_, col_id) = iter.next().unwrap(); + let node = ast.nodes.get_node(col_id).unwrap(); + assert_eq!(node.rule, Type::Column); let (_, proj_id) = iter.next().unwrap(); let node = ast.nodes.get_node(proj_id).unwrap(); diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index 30df0dfb62..6542d337e3 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -10,10 +10,9 @@ Query = _{ Except | UnionAll | Select | Values | Insert } ^"on" ~ Condition)? ~ (^"where" ~ Selection)? ~ (^"group" ~ ^"by" ~ GroupBy)? } - Projection = { (Asterisk | ArithmeticExprAlias | Column) ~ ("," ~ (Asterisk | ArithmeticExprAlias | Column))*? } - Column = { Alias | Value } - Alias = {Value ~ ^"as" ~ AliasName } - ArithmeticExprAlias = { ArithmeticExpr ~ (^"as" ~ AliasName)? } + Projection = { (Asterisk | Column) ~ ("," ~ (Asterisk | Column))*? } + Column = { Alias | Expr | ArithmeticExpr | Value } + Alias = {(Expr | Expr | ArithmeticExpr | Value) ~ ^"as" ~ AliasName } AliasName = @{ Name } Reference = { (ScanName ~ "." ~ ColumnName) | ColumnName } ColumnName = @{ Name } diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs index b969df9671..f8da7d43dd 100644 --- a/sbroad-core/src/ir/explain.rs +++ b/sbroad-core/src/ir/explain.rs @@ -19,6 +19,7 @@ use super::value::Value; #[derive(Debug, Serialize)] enum ColExpr { + Arithmetic(Box<ColExpr>, BinaryOp, Box<ColExpr>, bool), Column(String), Cast(Box<ColExpr>, CastType), Concat(Box<ColExpr>, Box<ColExpr>), @@ -30,6 +31,10 @@ enum ColExpr { impl Display for ColExpr { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let s = match &self { + ColExpr::Arithmetic(left, op, right, with_parentheses) => match with_parentheses { + false => format!("{left} {op} {right}"), + true => format!("({left} {op} {right})"), + }, ColExpr::Column(c) => c.to_string(), ColExpr::Cast(v, t) => format!("{v}::{t}"), ColExpr::Concat(l, r) => format!("{l} || {r}"), @@ -55,7 +60,7 @@ impl Default for ColExpr { } impl ColExpr { - #[allow(dead_code)] + #[allow(dead_code, clippy::too_many_lines)] fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> { let mut stack: Vec<ColExpr> = Vec::new(); let mut dft_post = @@ -133,10 +138,34 @@ impl ColExpr { let row_expr = ColExpr::Row(row); stack.push(row_expr); } - Expression::Alias { .. } - | Expression::Bool { .. } - | Expression::Arithmetic { .. } - | Expression::Unary { .. } => { + Expression::Arithmetic { + left: _, + op, + right: _, + with_parentheses, + } => { + let right = stack.pop().ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues( + "stack is empty while processing ARITHMETIC expression".to_string(), + ) + })?; + + let left = stack.pop().ok_or_else(|| { + SbroadError::UnexpectedNumberOfValues( + "stack is empty while processing ARITHMETIC expression".to_string(), + ) + })?; + + let ar_expr = ColExpr::Arithmetic( + Box::new(left), + BinaryOp::ArithOp(op.clone()), + Box::new(right), + *with_parentheses, + ); + + stack.push(ar_expr); + } + Expression::Alias { .. } | Expression::Bool { .. } | Expression::Unary { .. } => { return Err(SbroadError::Unsupported( Entity::Expression, Some(format!( diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs index f64006281a..3b7882aa11 100644 --- a/sbroad-core/src/ir/expression.rs +++ b/sbroad-core/src/ir/expression.rs @@ -416,11 +416,15 @@ impl Nodes { ))); } } - Node::Expression(Expression::Arithmetic { .. }) => {} + Node::Expression( + Expression::Arithmetic { .. } + | Expression::Bool { .. } + | Expression::Unary { .. }, + ) => {} _ => { return Err(SbroadError::Invalid( Entity::Node, - Some("node is not Alias or Arithmetic type".into()), + Some("node is not Alias, Arithmetic, Bool or Unary Expression type".into()), )); } } diff --git a/sbroad-core/tests/artifactory/backend/sql/tree/arbitrary_projection_plan.yaml b/sbroad-core/tests/artifactory/backend/sql/tree/arbitrary_projection_plan.yaml new file mode 100644 index 0000000000..21025d5ce3 --- /dev/null +++ b/sbroad-core/tests/artifactory/backend/sql/tree/arbitrary_projection_plan.yaml @@ -0,0 +1,187 @@ +nodes: + arena: + # 0 + - Expression: + Reference: + parent: 11 + targets: ~ + position: 0 + # 1 + - Expression: + Alias: + name: a + child: 0 + # 2 + - Expression: + Reference: + parent: 11 + targets: ~ + position: 1 + # 3 + - Expression: + Alias: + name: b + child: 2 + # 4 + - Expression: + Reference: + parent: 11 + targets: ~ + position: 2 + # 5 + - Expression: + Alias: + name: c + child: 4 + # 6 + - Expression: + Reference: + parent: 11 + targets: ~ + position: 3 + # 7 + - Expression: + Alias: + name: d + child: 6 + # 8 + - Expression: + Reference: + parent: 11 + targets: ~ + position: 4 + # 9 + - Expression: + Alias: + name: bucket_id + child: 8 + # 10 + - Expression: + Row: + list: + - 1 + - 3 + - 5 + - 7 + - 9 + distribution: ~ + # 11 + - Relational: + ScanRelation: + output: 10 + relation: t + # 12 + - Expression: + Reference: + parent: 25 + targets: [0] + position: 0 + # 13 + - Expression: + Row: + list: + - 12 + distribution: ~ + # 14 + - Expression: + Reference: + parent: 25 + targets: [0] + position: 1 + # 15 + - Expression: + Row: + list: + - 14 + distribution: ~ + # 16 + - Expression: + Reference: + parent: 25 + targets: [0] + position: 2 + # 17 + - Expression: + Row: + list: + - 16 + distribution: ~ + # 18 + - Expression: + Reference: + parent: 25 + targets: [0] + position: 3 + # 19 + - Expression: + Row: + list: + - 18 + distribution: ~ + # 20 + - Expression: + Arithmetic: + left: 13 + op: add + right: 15 + with_parentheses: false + # 21 + - Expression: + Bool: + left: 20 + op: gt + right: 17 + # 22 + - Expression: + Unary: + op: isnotnull + child: 19 + # 23 + - Expression: + Bool: + left: 21 + op: and + right: 22 + # 24 + - Expression: + Row: + list: + - 23 + distribution: ~ + # 25 + - Relational: + Projection: + children: + - 11 + output: 24 +relations: + tables: + t: + columns: + - name: a + type: Integer + role: User + - name: b + type: Integer + role: User + - name: c + type: Integer + role: User + - name: d + type: Integer + role: User + - name: bucket_id + type: Unsigned + role: Sharding + key: + positions: + - 0 + name: t + engine: Memtx +slices: + slices: [] +top: 25 +is_explain: false +undo: + log: {} +constants: {} diff --git a/sbroad-core/tests/artifactory/frontend/sql/arbitrary_projection_ast.yaml b/sbroad-core/tests/artifactory/frontend/sql/arbitrary_projection_ast.yaml new file mode 100644 index 0000000000..dfffc5db4c --- /dev/null +++ b/sbroad-core/tests/artifactory/frontend/sql/arbitrary_projection_ast.yaml @@ -0,0 +1,74 @@ +--- +nodes: + arena: + - children: + - 3 + rule: Select + value: ~ + - children: + - 2 + rule: Scan + value: ~ + - children: [] + rule: Table + value: t + - children: + - 1 + - 4 + rule: Projection + value: ~ + - children: + - 15 + rule: Column + value: ~ + - children: + - 8 + - 6 + rule: Gt + value: ~ + - children: + - 7 + rule: Reference + value: ~ + - children: [] + rule: ColumnName + value: "c" + - children: + - 12 + - 11 + - 9 + rule: Addition + value: ~ + - children: + - 10 + rule: Reference + value: ~ + - children: [] + rule: ColumnName + value: "b" + - children: [] + rule: Add + value: "+" + - children: + - 13 + rule: Reference + value: ~ + - children: [] + rule: ColumnName + value: "a" + - children: [] + rule: AliasName + value: "COLUMN_1" + - children: + - 5 + - 14 + rule: Alias + value: ~ +top: 3 +map: + 6: + - 1 + 9: + - 1 + 12: + - 1 \ No newline at end of file diff --git a/sbroad-core/tests/artifactory/frontend/sql/arithmetic_projection_ast.yaml b/sbroad-core/tests/artifactory/frontend/sql/arithmetic_projection_ast.yaml index 635c718464..02c751ce73 100644 --- a/sbroad-core/tests/artifactory/frontend/sql/arithmetic_projection_ast.yaml +++ b/sbroad-core/tests/artifactory/frontend/sql/arithmetic_projection_ast.yaml @@ -18,8 +18,8 @@ nodes: rule: Projection value: ~ - children: - - 5 - rule: ArithmeticExprAlias + - 12 + rule: Column value: ~ - children: - 9 @@ -44,6 +44,14 @@ nodes: - children: [] rule: ColumnName value: "a" + - children: [] + rule: AliasName + value: "COLUMN_1" + - children: + - 5 + - 11 + rule: Alias + value: ~ top: 3 map: 6: -- GitLab