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