From 2c5c7fc6dba09b5d12e9f8f2b507f5b953e36dee Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Mon, 11 Sep 2023 22:44:12 +0300
Subject: [PATCH] feat: refactor explain operator

---
 .../test/integration/explain_test.lua         |  49 ++-
 .../test/integration/left_outer_join_test.lua |   2 +-
 sbroad-core/src/frontend/sql/ir/tests.rs      |  64 ++--
 .../src/frontend/sql/ir/tests/params.rs       |   4 +-
 sbroad-core/src/ir/explain.rs                 | 359 +++++++-----------
 sbroad-core/src/ir/explain/tests.rs           |   2 +-
 sbroad-core/src/ir/explain/tests/concat.rs    |   4 +-
 7 files changed, 202 insertions(+), 282 deletions(-)

diff --git a/sbroad-cartridge/test_app/test/integration/explain_test.lua b/sbroad-cartridge/test_app/test/integration/explain_test.lua
index fd14121ca4..861428caed 100644
--- a/sbroad-cartridge/test_app/test/integration/explain_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/explain_test.lua
@@ -324,7 +324,7 @@ g.test_explain_arithmetic_projection = function()
     t.assert_equals(
         r,
         {
-            "projection ((\"arithmetic_space\".\"id\"::integer) + (2::unsigned) -> \"COL_1\")",
+            "projection (ROW(\"arithmetic_space\".\"id\"::integer) + ROW(2::unsigned) -> \"COL_1\")",
             "    scan \"arithmetic_space\"",
             "execution options:",
             "sql_vdbe_max_steps = 45000",
@@ -338,9 +338,9 @@ g.test_explain_arithmetic_projection = function()
     t.assert_equals(err, nil)
     t.assert_equals(
         r,
-        -- luacheck: max line length 160
+        -- luacheck: max line length 210
         {
-            "projection ((\"arithmetic_space\".\"a\"::integer) + (\"arithmetic_space\".\"b\"::integer) * (\"arithmetic_space\".\"c\"::integer) -> \"COL_1\")",
+            "projection (ROW(\"arithmetic_space\".\"a\"::integer) + ROW(\"arithmetic_space\".\"b\"::integer) * ROW(\"arithmetic_space\".\"c\"::integer) -> \"COL_1\")",
             "    scan \"arithmetic_space\"",
             "execution options:",
             "sql_vdbe_max_steps = 45000",
@@ -354,29 +354,44 @@ g.test_explain_arithmetic_projection = function()
     t.assert_equals(err, nil)
     t.assert_equals(
         r,
-        -- luacheck: max line length 160
+        -- luacheck: max line length 210
         {
-            "projection (((\"arithmetic_space\".\"a\"::integer) + (\"arithmetic_space\".\"b\"::integer)) * (\"arithmetic_space\".\"c\"::integer) -> \"COL_1\")",
+            "projection ((ROW(\"arithmetic_space\".\"a\"::integer) + ROW(\"arithmetic_space\".\"b\"::integer)) * ROW(\"arithmetic_space\".\"c\"::integer) -> \"COL_1\")",
             "    scan \"arithmetic_space\"",
             "execution options:",
             "sql_vdbe_max_steps = 45000",
             "vtable_max_rows = 5000",
         }
     )
-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", {
+    local r, err = api:call("sbroad.execute", {
         [[EXPLAIN select "a" > "b" from "arithmetic_space"]], {}
     })
-    t.assert_str_contains(tostring(err), "is not supported for yet")
+    t.assert_equals(err, nil)
+    t.assert_equals(
+        r,
+        -- luacheck: max line length 160
+        {
+            "projection (ROW(\"arithmetic_space\".\"a\"::integer) > ROW(\"arithmetic_space\".\"b\"::integer) -> \"COL_1\")",
+            "    scan \"arithmetic_space\"",
+            "execution options:",
+            "sql_vdbe_max_steps = 45000",
+            "vtable_max_rows = 5000",
+        }
+    )
 
-    -- 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")
+    local r, err = api:call("sbroad.execute", {
+        [[EXPLAIN select "a" is null from "arithmetic_space"]], {}
+    })
+    t.assert_equals(err, nil)
+    t.assert_equals(
+        r,
+        {
+            "projection (ROW(\"arithmetic_space\".\"a\"::integer) is null -> \"COL_1\")",
+            "    scan \"arithmetic_space\"",
+            "execution options:",
+            "sql_vdbe_max_steps = 45000",
+            "vtable_max_rows = 5000",
+        }
+    )
 end
diff --git a/sbroad-cartridge/test_app/test/integration/left_outer_join_test.lua b/sbroad-cartridge/test_app/test/integration/left_outer_join_test.lua
index c69df96443..3aa8d73438 100644
--- a/sbroad-cartridge/test_app/test/integration/left_outer_join_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/left_outer_join_test.lua
@@ -417,7 +417,7 @@ left_join.test_sq_with_full_motion = function()
         "subquery $0:",
         "motion [policy: full]",
         "            scan",
-        "                projection ((\"arithmetic_space\".\"a\"::integer) + (1::unsigned) -> \"COL_1\")",
+        "                projection (ROW(\"arithmetic_space\".\"a\"::integer) + ROW(1::unsigned) -> \"COL_1\")",
         "                    scan \"arithmetic_space\"",
         "execution options:",
         "sql_vdbe_max_steps = 45000",
diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs
index 2a9fbfde57..929e2f1a0e 100644
--- a/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -35,7 +35,7 @@ fn front_sql2() {
 
     let expected_explain = String::from(
         r#"projection ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code")
-    selection ROW("hash_testing"."identification_number"::integer) = ROW(1::unsigned) and ROW("hash_testing"."product_code"::string) = ROW('1'::string) or ROW("hash_testing"."identification_number"::integer) = ROW(2::unsigned) and ROW("hash_testing"."product_code"::string) = ROW('2'::string)
+    selection (ROW("hash_testing"."identification_number"::integer) = ROW(1::unsigned) and ROW("hash_testing"."product_code"::string) = ROW('1'::string) or ROW("hash_testing"."identification_number"::integer) = ROW(2::unsigned) and ROW("hash_testing"."product_code"::string) = ROW('2'::string))
         scan "hash_testing"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -100,7 +100,7 @@ fn front_sql4() {
 
     let expected_explain = String::from(
         r#"projection ("t3"."identification_number"::integer -> "identification_number", "t3"."product_code"::string -> "product_code")
-    selection ROW("t3"."identification_number"::integer) = ROW(1::unsigned) or ROW("t3"."identification_number"::integer) = ROW(2::unsigned) or ROW("t3"."identification_number"::integer) = ROW(3::unsigned) and ROW("t3"."product_code"::string) = ROW('1'::string) or ROW("t3"."product_code"::string) = ROW('2'::string)
+    selection (ROW("t3"."identification_number"::integer) = ROW(1::unsigned) or (ROW("t3"."identification_number"::integer) = ROW(2::unsigned) or ROW("t3"."identification_number"::integer) = ROW(3::unsigned))) and (ROW("t3"."product_code"::string) = ROW('1'::string) or ROW("t3"."product_code"::string) = ROW('2'::string))
         scan "t3"
             union all
                 projection ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code")
@@ -772,7 +772,7 @@ fn front_sql_aggregates() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "b", (sum(("count_28"::integer))::decimal) + (sum(("count_30"::integer))::decimal) -> "COL_1")
+        r#"projection ("column_12"::unsigned -> "b", ROW(sum(("count_28"::integer))::decimal) + ROW(sum(("count_30"::integer))::decimal) -> "COL_1")
     group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "count_28"::integer -> "count_28", "count_30"::integer -> "count_30")
         motion [policy: segment([ref("column_12")])]
             scan
@@ -795,7 +795,7 @@ fn front_sql_avg_aggregate() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ((sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::integer::double))::decimal) -> "COL_1", avg(distinct ("column_15"::decimal::double))::decimal -> "COL_2", ((sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::integer::double))::decimal)) * ((sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::integer::double))::decimal)) -> "COL_3")
+        r#"projection ((sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::integer::double))::decimal) -> "COL_1", avg(distinct ("column_15"::decimal::double))::decimal -> "COL_2", ROW((sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::integer::double))::decimal)) * ROW((sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::integer::double))::decimal)) -> "COL_3")
     motion [policy: full]
         scan
             projection ("t"."b"::unsigned -> "column_15", sum(("t"."b"::unsigned))::decimal -> "sum_13", count(("t"."b"::unsigned))::integer -> "count_13")
@@ -992,7 +992,7 @@ fn front_sql_aggregates_with_subexpressions() {
     group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "count_39"::integer -> "count_39", "count_35"::integer -> "count_35")
         motion [policy: segment([ref("column_12")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", count(("FUNC"(("t"."a"::unsigned))::integer))::integer -> "count_39", count((("t"."a"::unsigned) * ("t"."b"::unsigned) + (1::unsigned)))::integer -> "count_35")
+                projection ("t"."b"::unsigned -> "column_12", count(("FUNC"(("t"."a"::unsigned))::integer))::integer -> "count_39", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::integer -> "count_35")
                     group by ("t"."b"::unsigned) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
@@ -1039,8 +1039,8 @@ fn front_sql_aggregates_with_distinct2() {
     group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "column_34"::unsigned -> "column_34")
         motion [policy: segment([ref("column_12")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", ("t"."a"::unsigned) + ("t"."b"::unsigned) + (3::unsigned) -> "column_34")
-                    group by ("t"."b"::unsigned, ("t"."a"::unsigned) + ("t"."b"::unsigned) + (3::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+                projection ("t"."b"::unsigned -> "column_12", ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned) -> "column_34")
+                    group by ("t"."b"::unsigned, ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1061,8 +1061,8 @@ fn front_sql_aggregates_with_distinct3() {
         r#"projection (sum(distinct ("column_19"::decimal))::decimal -> "COL_1")
     motion [policy: full]
         scan
-            projection (("t"."a"::unsigned) + ("t"."b"::unsigned) + (3::unsigned) -> "column_19")
-                group by (("t"."a"::unsigned) + ("t"."b"::unsigned) + (3::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+            projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned) -> "column_19")
+                group by (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                     scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1186,7 +1186,7 @@ fn front_sql_aggregate_without_groupby() {
         r#"projection (sum(("sum_20"::decimal))::decimal -> "COL_1")
     motion [policy: full]
         scan
-            projection (sum((("t"."a"::unsigned) * ("t"."b"::unsigned) + (1::unsigned)))::decimal -> "sum_20")
+            projection (sum((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::decimal -> "sum_20")
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1456,8 +1456,8 @@ fn front_sql_groupby_expression() {
     group by ("column_16"::unsigned) output: ("column_16"::unsigned -> "column_16")
         motion [policy: segment([ref("column_16")])]
             scan
-                projection (("t"."a"::unsigned) + ("t"."b"::unsigned) -> "column_16")
-                    group by (("t"."a"::unsigned) + ("t"."b"::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_16")
+                    group by (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1476,12 +1476,12 @@ fn front_sql_groupby_expression2() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_16"::unsigned + (sum(("count_35"::integer))::decimal) -> "COL_1")
+        r#"projection ("column_16"::unsigned + ROW(sum(("count_35"::integer))::decimal) -> "COL_1")
     group by ("column_16"::unsigned) output: ("column_16"::unsigned -> "column_16", "count_35"::integer -> "count_35")
         motion [policy: segment([ref("column_16")])]
             scan
-                projection ((("t"."a"::unsigned) + ("t"."b"::unsigned)) -> "column_16", count(("t"."a"::unsigned))::integer -> "count_35")
-                    group by ((("t"."a"::unsigned) + ("t"."b"::unsigned))) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+                projection ((ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned)) -> "column_16", count(("t"."a"::unsigned))::integer -> "count_35")
+                    group by ((ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned))) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1499,12 +1499,12 @@ fn front_sql_groupby_expression3() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection ("column_16"::unsigned -> "COL_1", "column_26"::unsigned * (sum(("sum_55"::decimal))::decimal) / (sum(("count_61"::integer))::decimal) -> "COL_2")
+        r#"projection ("column_16"::unsigned -> "COL_1", "column_26"::unsigned * ROW(sum(("sum_55"::decimal))::decimal) / ROW(sum(("count_61"::integer))::decimal) -> "COL_2")
     group by ("column_16"::unsigned, "column_26"::unsigned) output: ("column_26"::unsigned -> "column_26", "column_16"::unsigned -> "column_16", "sum_55"::decimal -> "sum_55", "count_61"::integer -> "count_61")
         motion [policy: segment([ref("column_16"), ref("column_26")])]
             scan
-                projection ((("t"."c"::unsigned) * ("t"."d"::unsigned)) -> "column_26", ("t"."a"::unsigned) + ("t"."b"::unsigned) -> "column_16", sum((("t"."c"::unsigned) * ("t"."d"::unsigned)))::decimal -> "sum_55", count((("t"."a"::unsigned) * ("t"."b"::unsigned)))::integer -> "count_61")
-                    group by (("t"."a"::unsigned) + ("t"."b"::unsigned), (("t"."c"::unsigned) * ("t"."d"::unsigned))) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+                projection ((ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)) -> "column_26", ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_16", sum((ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)))::decimal -> "sum_55", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned)))::integer -> "count_61")
+                    group by (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned), (ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned))) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1526,8 +1526,8 @@ fn front_sql_groupby_expression4() {
     group by ("column_16"::unsigned, "column_17"::unsigned) output: ("column_17"::unsigned -> "column_17", "column_16"::unsigned -> "column_16")
         motion [policy: segment([ref("column_16"), ref("column_17")])]
             scan
-                projection ("t"."a"::unsigned -> "column_17", ("t"."a"::unsigned) + ("t"."b"::unsigned) -> "column_16")
-                    group by (("t"."a"::unsigned) + ("t"."b"::unsigned), "t"."a"::unsigned) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+                projection ("t"."a"::unsigned -> "column_17", ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_16")
+                    group by (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned), "t"."a"::unsigned) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1655,7 +1655,7 @@ fn front_sql_left_join_single_left() {
     left join on ROW("T1"."A"::decimal) = ROW("T2"."B"::unsigned)
         motion [policy: segment([ref("A")])]
             scan "T1"
-                projection ((sum(("sum_13"::decimal))::decimal) / (3::unsigned) -> "A")
+                projection (ROW(sum(("sum_13"::decimal))::decimal) / ROW(3::unsigned) -> "A")
                     motion [policy: full]
                         scan
                             projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_13")
@@ -1689,7 +1689,7 @@ fn front_sql_left_join_single_left2() {
     left join on ROW("T1"."A"::decimal) + ROW(3::unsigned) <> ROW("T2"."B"::unsigned)
         motion [policy: segment([ref("A")])]
             scan "T1"
-                projection ((sum(("sum_13"::decimal))::decimal) / (3::unsigned) -> "A")
+                projection (ROW(sum(("sum_13"::decimal))::decimal) / ROW(3::unsigned) -> "A")
                     motion [policy: full]
                         scan
                             projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_13")
@@ -1723,7 +1723,7 @@ fn front_sql_left_join_single_both() {
     left join on ROW("T1"."A"::decimal) <> ROW("T2"."B"::integer)
         motion [policy: segment([ref("A")])]
             scan "T1"
-                projection ((sum(("sum_13"::decimal))::decimal) / (3::unsigned) -> "A")
+                projection (ROW(sum(("sum_13"::decimal))::decimal) / ROW(3::unsigned) -> "A")
                     motion [policy: full]
                         scan
                             projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_13")
@@ -1809,7 +1809,7 @@ fn front_sql_having2() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection ((sum(("sum_38"::decimal))::decimal) * (count(distinct ("column_39"::integer))::integer) -> "COL_1", sum(("sum_38"::decimal))::decimal -> "COL_2")
+        r#"projection (ROW(sum(("sum_38"::decimal))::decimal) * ROW(count(distinct ("column_39"::integer))::integer) -> "COL_1", sum(("sum_38"::decimal))::decimal -> "COL_2")
     having ROW(sum(distinct ("column_39"::decimal))::decimal) > ROW(1::unsigned) and ROW(sum(("sum_38"::decimal))::decimal) > ROW(1::unsigned)
         motion [policy: full]
             scan
@@ -2005,7 +2005,7 @@ fn front_sql_unique_local_aggregates() {
     let plan = sql_to_optimized_ir(input, vec![]);
     // here we must compute only two aggregates at local stage: sum(a), count(a)
     let expected_explain = String::from(
-        r#"projection (sum(("sum_13"::decimal))::decimal -> "COL_1", sum(("count_16"::integer))::decimal -> "COL_2", (sum(("sum_13"::decimal))::decimal) + (sum(("count_16"::integer))::decimal) -> "COL_3")
+        r#"projection (sum(("sum_13"::decimal))::decimal -> "COL_1", sum(("count_16"::integer))::decimal -> "COL_2", ROW(sum(("sum_13"::decimal))::decimal) + ROW(sum(("count_16"::integer))::decimal) -> "COL_3")
     motion [policy: full]
         scan
             projection (count(("t"."a"::unsigned))::integer -> "count_16", sum(("t"."a"::unsigned))::decimal -> "sum_13")
@@ -2097,8 +2097,8 @@ fn front_sql_select_distinct() {
     group by ("column_22"::unsigned, "column_27"::unsigned) output: ("column_22"::unsigned -> "column_22", "column_27"::unsigned -> "column_27")
         motion [policy: segment([ref("column_22"), ref("column_27")])]
             scan
-                projection ("t"."a"::unsigned -> "column_22", ("t"."a"::unsigned) + ("t"."b"::unsigned) -> "column_27")
-                    group by ("t"."a"::unsigned, ("t"."a"::unsigned) + ("t"."b"::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
+                projection ("t"."a"::unsigned -> "column_22", ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_27")
+                    group by ("t"."a"::unsigned, ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2241,7 +2241,7 @@ fn front_sql_insert_1() {
         r#"insert "t" on conflict: fail
     motion [policy: segment([value(NULL), ref("a")])]
         projection ("t"."a"::unsigned -> "a")
-            selection ROW("t"."a"::unsigned) = ROW(1::unsigned) and ROW("t"."b"::unsigned) = ROW(2::unsigned) or ROW("t"."a"::unsigned) = ROW(2::unsigned) and ROW("t"."b"::unsigned) = ROW(3::unsigned)
+            selection (ROW("t"."a"::unsigned) = ROW(1::unsigned) and ROW("t"."b"::unsigned) = ROW(2::unsigned) or ROW("t"."a"::unsigned) = ROW(2::unsigned) and ROW("t"."b"::unsigned) = ROW(3::unsigned))
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2282,7 +2282,7 @@ fn front_sql_insert_3() {
         r#"insert "t" on conflict: fail
     motion [policy: segment([ref("b"), ref("a")])]
         projection ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b")
-            selection ROW("t"."a"::unsigned) = ROW(1::unsigned) and ROW("t"."b"::unsigned) = ROW(2::unsigned) or ROW("t"."a"::unsigned) = ROW(3::unsigned) and ROW("t"."b"::unsigned) = ROW(4::unsigned)
+            selection (ROW("t"."a"::unsigned) = ROW(1::unsigned) and ROW("t"."b"::unsigned) = ROW(2::unsigned) or ROW("t"."a"::unsigned) = ROW(3::unsigned) and ROW("t"."b"::unsigned) = ROW(4::unsigned))
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2438,7 +2438,7 @@ fn front_sql_update2() {
         r#"update "t"
 "c" = COL_0
     motion [policy: local]
-        projection (("t"."a"::unsigned) + ("t"."b"::unsigned) -> COL_0, "t"."b"::unsigned -> COL_1)
+        projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> COL_0, "t"."b"::unsigned -> COL_1)
             scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2457,7 +2457,7 @@ fn front_sql_update3() {
         r#"update "t"
 "c" = COL_0
     motion [policy: local]
-        projection (("t"."a"::unsigned) + ("t"."b"::unsigned) -> COL_0, "t"."b"::unsigned -> COL_1)
+        projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> COL_0, "t"."b"::unsigned -> COL_1)
             selection ROW("t"."c"::unsigned) = ROW(1::unsigned)
                 scan "t"
 execution options:
@@ -2482,7 +2482,7 @@ fn front_sql_update4() {
 "d" = COL_0
 "c" = COL_0
     motion [policy: local]
-        projection (("b1"::integer) * (2::unsigned) -> COL_0, "t"."b"::unsigned -> COL_1)
+        projection (ROW("b1"::integer) * ROW(2::unsigned) -> COL_0, "t"."b"::unsigned -> COL_1)
             join on ROW("t"."c"::unsigned) = ROW("b1"::integer)
                 scan "t"
                     projection ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d")
diff --git a/sbroad-core/src/frontend/sql/ir/tests/params.rs b/sbroad-core/src/frontend/sql/ir/tests/params.rs
index 1d20bda305..076554d26f 100644
--- a/sbroad-core/src/frontend/sql/ir/tests/params.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests/params.rs
@@ -101,7 +101,7 @@ fn front_params5() {
 
     let expected_explain = String::from(
         r#"projection ("test_space"."id"::unsigned -> "id")
-    selection ROW("test_space"."sys_op"::unsigned) = ROW(0::integer) or ROW("test_space"."id"::unsigned) in ROW($0)
+    selection (ROW("test_space"."sys_op"::unsigned) = ROW(0::integer) or ROW("test_space"."id"::unsigned) in ROW($0))
         scan "test_space"
 subquery $0:
 motion [policy: segment([ref("sysFrom")])]
@@ -138,7 +138,7 @@ fn front_params6() {
 
     let expected_explain = String::from(
         r#"projection ("test_space"."id"::unsigned -> "id")
-    selection ROW("test_space"."sys_op"::unsigned) = ROW(0::integer) or ROW("test_space"."id"::unsigned) not in ROW($0)
+    selection (ROW("test_space"."sys_op"::unsigned) = ROW(0::integer) or ROW("test_space"."id"::unsigned) not in ROW($0))
         scan "test_space"
 subquery $0:
 motion [policy: full]
diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs
index 7c020e08f9..129c5b4c3d 100644
--- a/sbroad-core/src/ir/explain.rs
+++ b/sbroad-core/src/ir/explain.rs
@@ -1,5 +1,6 @@
 use std::collections::HashMap;
 use std::fmt::{Display, Formatter, Write as _};
+use std::mem::take;
 
 use itertools::Itertools;
 use serde::Serialize;
@@ -22,30 +23,46 @@ use super::value::Value;
 enum ColExpr {
     Alias(Box<ColExpr>, String),
     Arithmetic(Box<ColExpr>, BinaryOp, Box<ColExpr>, bool),
+    Bool(Box<ColExpr>, BinaryOp, Box<ColExpr>),
+    Unary(Unary, Box<ColExpr>),
     Column(String, Type),
     Cast(Box<ColExpr>, CastType),
     Concat(Box<ColExpr>, Box<ColExpr>),
-    StableFunction(String, Box<ColExpr>, bool, Type),
-    Row(Vec<ColExpr>),
+    StableFunction(String, Vec<ColExpr>, bool, Type),
+    Row(Row),
     None,
 }
 
 impl Display for ColExpr {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         let s = match &self {
-            ColExpr::Alias(expr, name) => format!("({expr} -> {name})"),
+            ColExpr::Alias(expr, name) => format!("{expr} -> {name}"),
             ColExpr::Arithmetic(left, op, right, with_parentheses) => match with_parentheses {
                 false => format!("{left} {op} {right}"),
                 true => format!("({left} {op} {right})"),
             },
+            ColExpr::Bool(left, op, right) => {
+                if let BinaryOp::BoolOp(Bool::Or) = op {
+                    format!("({left} {op} {right})")
+                } else {
+                    format!("{left} {op} {right}")
+                }
+            }
+            ColExpr::Unary(op, expr) => match op {
+                Unary::IsNull | Unary::IsNotNull => format!("{expr} {op}"),
+                Unary::Exists | Unary::NotExists => format!("{op} {expr}"),
+            },
             ColExpr::Column(c, col_type) => format!("{c}::{col_type}"),
             ColExpr::Cast(v, t) => format!("{v}::{t}"),
             ColExpr::Concat(l, r) => format!("{l} || {r}"),
-            ColExpr::StableFunction(name, arg, is_distinct, func_type) => format!(
-                "{name}({}{arg})::{func_type}",
-                if *is_distinct { "distinct " } else { "" }
-            ),
-            ColExpr::Row(list) => format!("({})", list.iter().format(", ")),
+            ColExpr::StableFunction(name, args, is_distinct, func_type) => {
+                let formatted_args = format!("({})", args.iter().format(", "));
+                format!(
+                    "{name}({}{formatted_args})::{func_type}",
+                    if *is_distinct { "distinct " } else { "" }
+                )
+            }
+            ColExpr::Row(row) => row.to_string(),
             ColExpr::None => String::new(),
         };
 
@@ -67,8 +84,12 @@ impl Default for ColExpr {
 
 impl ColExpr {
     #[allow(dead_code, clippy::too_many_lines)]
-    fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> {
-        let mut stack: Vec<ColExpr> = Vec::new();
+    fn new(
+        plan: &Plan,
+        subtree_top: usize,
+        sq_ref_map: &SubQueryRefMap,
+    ) -> Result<Self, SbroadError> {
+        let mut stack: Vec<(ColExpr, usize)> = Vec::new();
         let mut dft_post =
             PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, false), EXPR_CAPACITY);
 
@@ -77,19 +98,18 @@ impl ColExpr {
 
             match &current_node {
                 Expression::Cast { to, .. } => {
-                    let expr = stack.pop().ok_or_else(|| {
+                    let (expr, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing CAST expression".to_string(),
                         )
                     })?;
                     let cast_expr = ColExpr::Cast(Box::new(expr), to.clone());
-                    stack.push(cast_expr);
+                    stack.push((cast_expr, id));
                 }
                 Expression::CountAsterisk => {
-                    stack.push(ColExpr::Column(
-                        "*".to_string(),
-                        current_node.calculate_type(plan)?,
-                    ));
+                    let count_asterisk_expr =
+                        ColExpr::Column("*".to_string(), current_node.calculate_type(plan)?);
+                    stack.push((count_asterisk_expr, id));
                 }
                 Expression::Reference { position, .. } => {
                     let mut col_name = String::new();
@@ -105,29 +125,27 @@ impl ColExpr {
                     let alias = plan.get_alias_from_reference_node(current_node)?;
                     col_name.push_str(alias);
 
-                    stack.push(ColExpr::Column(
-                        col_name,
-                        current_node.calculate_type(plan)?,
-                    ));
+                    let ref_expr = ColExpr::Column(col_name, current_node.calculate_type(plan)?);
+                    stack.push((ref_expr, id));
                 }
                 Expression::Concat { .. } => {
-                    let right = stack.pop().ok_or_else(|| {
+                    let (right, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing CONCAT expression".to_string(),
                         )
                     })?;
-                    let left = stack.pop().ok_or_else(|| {
+                    let (left, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing CONCAT expression".to_string(),
                         )
                     })?;
                     let concat_expr = ColExpr::Concat(Box::new(left), Box::new(right));
-                    stack.push(concat_expr);
+                    stack.push((concat_expr, id));
                 }
                 Expression::Constant { value } => {
                     let expr =
                         ColExpr::Column(value.to_string(), current_node.calculate_type(plan)?);
-                    stack.push(expr);
+                    stack.push((expr, id));
                 }
                 Expression::StableFunction {
                     name,
@@ -138,7 +156,7 @@ impl ColExpr {
                     let mut len = children.len();
                     let mut args: Vec<ColExpr> = Vec::with_capacity(len);
                     while len > 0 {
-                        let arg = stack.pop().ok_or_else(|| {
+                        let (arg, _) = stack.pop().ok_or_else(|| {
                             SbroadError::UnexpectedNumberOfValues(
                                 format!("stack is empty, expected to pop {len} element while processing STABLE FUNCTION expression"),
                             )
@@ -147,18 +165,17 @@ impl ColExpr {
                         len -= 1;
                     }
                     args.reverse();
-                    let args_expr = ColExpr::Row(args);
                     let func_expr = ColExpr::StableFunction(
                         name.clone(),
-                        Box::new(args_expr),
+                        args,
                         *is_distinct,
                         func_type.clone(),
                     );
-                    stack.push(func_expr);
+                    stack.push((func_expr, id));
                 }
                 Expression::Row { list, .. } => {
                     let mut len = list.len();
-                    let mut row: Vec<ColExpr> = Vec::with_capacity(len);
+                    let mut row: Vec<(ColExpr, usize)> = Vec::with_capacity(len);
                     while len > 0 {
                         let expr = stack.pop().ok_or_else(|| {
                             SbroadError::UnexpectedNumberOfValues(
@@ -168,8 +185,10 @@ impl ColExpr {
                         row.push(expr);
                         len -= 1;
                     }
+                    row.reverse();
+                    let row = Row::from_col_exprs_with_ids(plan, &mut row, sq_ref_map)?;
                     let row_expr = ColExpr::Row(row);
-                    stack.push(row_expr);
+                    stack.push((row_expr, id));
                 }
                 Expression::Arithmetic {
                     left: _,
@@ -177,13 +196,13 @@ impl ColExpr {
                     right: _,
                     with_parentheses,
                 } => {
-                    let right = stack.pop().ok_or_else(|| {
+                    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(|| {
+                    let (left, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing ARITHMETIC expression".to_string(),
                         )
@@ -196,91 +215,81 @@ impl ColExpr {
                         *with_parentheses,
                     );
 
-                    stack.push(ar_expr);
+                    stack.push((ar_expr, id));
                 }
                 Expression::Alias { name, .. } => {
-                    let expr = stack.pop().ok_or_else(|| {
+                    let (expr, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing ALIAS expression".to_string(),
                         )
                     })?;
                     let alias_expr = ColExpr::Alias(Box::new(expr), name.clone());
-                    stack.push(alias_expr);
+                    stack.push((alias_expr, id));
                 }
-                Expression::Bool { .. } | Expression::Unary { .. } => {
-                    return Err(SbroadError::Unsupported(
-                        Entity::Expression,
-                        Some(format!(
-                            "Column expression node [{current_node:?}] is not supported for yet"
-                        )),
-                    ));
-                }
-            }
-        }
-
-        stack
-            .pop()
-            .ok_or_else(|| SbroadError::UnexpectedNumberOfValues("stack is empty".to_string()))
-    }
-}
-
-#[derive(Debug, Serialize, Default)]
-struct Col {
-    /// Column alias from sql query
-    alias: Option<String>,
-
-    /// Column expression (e.g. column name, function, etc.)
-    col: ColExpr,
-}
+                Expression::Bool { op, .. } => {
+                    let (right, _) = stack.pop().ok_or_else(|| {
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing BOOL expression".to_string(),
+                        )
+                    })?;
 
-impl Col {
-    #[allow(dead_code)]
-    fn new(plan: &Plan, subtree_top: usize) -> Result<Self, SbroadError> {
-        let mut column = Col::default();
+                    let (left, _) = stack.pop().ok_or_else(|| {
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing BOOL expression".to_string(),
+                        )
+                    })?;
 
-        let mut dft_post =
-            PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, true), EXPR_CAPACITY);
-        for (_, id) in dft_post.iter(subtree_top) {
-            let current_node = plan.get_expression_node(id)?;
+                    let bool_expr = ColExpr::Bool(
+                        Box::new(left),
+                        BinaryOp::BoolOp(op.clone()),
+                        Box::new(right),
+                    );
 
-            if let Expression::Alias { name, .. } = &current_node {
-                column.alias = Some(name.to_string());
-            } else {
-                column.col = ColExpr::new(plan, id)?;
+                    stack.push((bool_expr, id));
+                }
+                Expression::Unary { op, .. } => {
+                    let (expr, _) = stack.pop().ok_or_else(|| {
+                        SbroadError::UnexpectedNumberOfValues(
+                            "stack is empty while processing UNARY expression".to_string(),
+                        )
+                    })?;
+                    let alias_expr = ColExpr::Unary(op.clone(), Box::new(expr));
+                    stack.push((alias_expr, id));
+                }
             }
         }
 
-        Ok(column)
+        let (expr, _) = stack
+            .pop()
+            .ok_or_else(|| SbroadError::UnexpectedNumberOfValues("stack is empty".to_string()))?;
+        Ok(expr)
     }
 }
 
-impl Display for Col {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        let mut s = String::from(&self.col);
-
-        if let Some(a) = &self.alias {
-            write!(s, " -> {a}")?;
-        }
-
-        write!(f, "{s}")
-    }
-}
+/// Alias for map of (`SubQuery` id -> it's offset).
+/// Offset = `SubQuery` index (e.g. in case there are several `SubQueries` in Selection WHERE condition
+/// index will indicate to which of them Reference is pointing).
+type SubQueryRefMap = HashMap<usize, usize>;
 
 #[derive(Debug, Serialize)]
 struct Projection {
     /// List of colums in sql query
-    cols: Vec<Col>,
+    cols: Vec<ColExpr>,
 }
 
 impl Projection {
     #[allow(dead_code)]
-    fn new(plan: &Plan, output_id: usize) -> Result<Self, SbroadError> {
+    fn new(
+        plan: &Plan,
+        output_id: usize,
+        sq_ref_map: &SubQueryRefMap,
+    ) -> Result<Self, SbroadError> {
         let mut result = Projection { cols: vec![] };
 
         let alias_list = plan.get_expression_node(output_id)?;
 
         for col_node_id in alias_list.get_row_list()? {
-            let col = Col::new(plan, *col_node_id)?;
+            let col = ColExpr::new(plan, *col_node_id, sq_ref_map)?;
 
             result.cols.push(col);
         }
@@ -307,25 +316,30 @@ impl Display for Projection {
 #[derive(Debug, Serialize)]
 struct GroupBy {
     /// List of colums in sql query
-    gr_cols: Vec<Col>,
-    output_cols: Vec<Col>,
+    gr_cols: Vec<ColExpr>,
+    output_cols: Vec<ColExpr>,
 }
 
 impl GroupBy {
     #[allow(dead_code)]
-    fn new(plan: &Plan, gr_cols: &Vec<usize>, output_id: usize) -> Result<Self, SbroadError> {
+    fn new(
+        plan: &Plan,
+        gr_cols: &Vec<usize>,
+        output_id: usize,
+        sq_ref_map: &SubQueryRefMap,
+    ) -> Result<Self, SbroadError> {
         let mut result = GroupBy {
             gr_cols: vec![],
             output_cols: vec![],
         };
 
         for col_node_id in gr_cols {
-            let col = Col::new(plan, *col_node_id)?;
+            let col = ColExpr::new(plan, *col_node_id, sq_ref_map)?;
             result.gr_cols.push(col);
         }
         let alias_list = plan.get_expression_node(output_id)?;
         for col_node_id in alias_list.get_row_list()? {
-            let col = Col::new(plan, *col_node_id)?;
+            let col = ColExpr::new(plan, *col_node_id, sq_ref_map)?;
             result.output_cols.push(col);
         }
         Ok(result)
@@ -495,16 +509,14 @@ impl Display for Ref {
 
 #[derive(Debug, Serialize)]
 enum RowVal {
-    Const(Value),
-    Column(Col),
+    ColumnExpr(ColExpr),
     SqRef(Ref),
 }
 
 impl Display for RowVal {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         let s = match &self {
-            RowVal::Const(c) => format!("{c}::{}", c.get_type()),
-            RowVal::Column(c) => c.to_string(),
+            RowVal::ColumnExpr(c) => c.to_string(),
             RowVal::SqRef(r) => r.to_string(),
         };
 
@@ -528,29 +540,26 @@ impl Row {
         self.cols.push(row);
     }
 
-    fn from_ir_nodes(
+    fn from_col_exprs_with_ids(
         plan: &Plan,
-        node_ids: &[usize],
-        ref_map: &HashMap<usize, usize>,
+        exprs_with_ids: &mut Vec<(ColExpr, usize)>,
+        sq_ref_map: &SubQueryRefMap,
     ) -> Result<Self, SbroadError> {
         let mut row = Row::new();
 
-        for child in node_ids {
-            let current_node = plan.get_expression_node(*child)?;
+        for (col_expr, expr_id) in take(exprs_with_ids) {
+            let current_node = plan.get_expression_node(expr_id)?;
 
             match &current_node {
-                Expression::Constant { value, .. } => {
-                    row.add_col(RowVal::Const(value.clone()));
-                }
                 Expression::Reference { .. } => {
-                    let rel_id: usize = *plan.get_relational_from_reference_node(*child)?;
+                    let rel_id: usize = *plan.get_relational_from_reference_node(expr_id)?;
 
                     let rel_node = plan.get_relation_node(rel_id)?;
                     if plan.is_additional_child(rel_id)? {
                         if let Relational::ScanSubQuery { .. } | Relational::Motion { .. } =
                             rel_node
                         {
-                            let sq_offset = ref_map.get(&rel_id).ok_or_else(|| {
+                            let sq_offset = sq_ref_map.get(&rel_id).ok_or_else(|| {
                                 SbroadError::NotFound(
                                     Entity::SubQuery,
                                     format!("with index {rel_id} in the map"),
@@ -566,27 +575,11 @@ impl Row {
                             ));
                         }
                     } else {
-                        let col = Col::new(plan, *child)?;
-                        row.add_col(RowVal::Column(col));
+                        let col = ColExpr::new(plan, expr_id, sq_ref_map)?;
+                        row.add_col(RowVal::ColumnExpr(col));
                     }
                 }
-                Expression::Bool { .. }
-                | Expression::Arithmetic { .. }
-                | Expression::Cast { .. }
-                | Expression::Concat { .. }
-                | Expression::StableFunction { .. }
-                | Expression::Row { .. }
-                | Expression::Alias { .. }
-                | Expression::Unary { .. } => {
-                    let col = Col::new(plan, *child)?;
-                    row.add_col(RowVal::Column(col));
-                }
-                Expression::CountAsterisk => {
-                    return Err(SbroadError::Invalid(
-                        Entity::Plan,
-                        Some("CountAsterisk can't be present among Row children!".into()),
-                    ))
-                }
+                _ => row.add_col(RowVal::ColumnExpr(col_expr)),
             }
         }
 
@@ -612,71 +605,6 @@ enum BinaryOp {
     ArithOp(Arithmetic),
     BoolOp(Bool),
 }
-/// Recursive type which describe `WHERE` cause in explain
-#[derive(Debug, Serialize)]
-enum Selection {
-    Row(Row),
-    BinaryOp {
-        left: Box<Selection>,
-        op: BinaryOp,
-        right: Box<Selection>,
-    },
-    UnaryOp {
-        op: Unary,
-        child: Box<Selection>,
-    },
-}
-
-impl Selection {
-    #[allow(dead_code)]
-    fn new(
-        plan: &Plan,
-        subtree_node_id: usize,
-        ref_map: &HashMap<usize, usize>,
-    ) -> Result<Self, SbroadError> {
-        let current_node = plan.get_expression_node(subtree_node_id)?;
-
-        let result = match current_node {
-            Expression::Bool { left, op, right } => Selection::BinaryOp {
-                left: Box::new(Selection::new(plan, *left, ref_map)?),
-                op: BinaryOp::BoolOp(op.clone()),
-                right: Box::new(Selection::new(plan, *right, ref_map)?),
-            },
-            Expression::Arithmetic {
-                left, op, right, ..
-            } => Selection::BinaryOp {
-                left: Box::new(Selection::new(plan, *left, ref_map)?),
-                op: BinaryOp::ArithOp(op.clone()),
-                right: Box::new(Selection::new(plan, *right, ref_map)?),
-            },
-            Expression::Row { list, .. } => {
-                let row = Row::from_ir_nodes(plan, list, ref_map)?;
-                Selection::Row(row)
-            }
-            Expression::Unary { op, child } => Selection::UnaryOp {
-                op: op.clone(),
-                child: Box::new(Selection::new(plan, *child, ref_map)?),
-            },
-            Expression::Reference { .. }
-            | Expression::Cast { .. }
-            | Expression::StableFunction { .. }
-            | Expression::Concat { .. }
-            | Expression::Constant { .. }
-            | Expression::Alias { .. } => {
-                let row = Row::from_ir_nodes(plan, &[subtree_node_id], ref_map)?;
-                Selection::Row(row)
-            }
-            Expression::CountAsterisk => {
-                return Err(SbroadError::Invalid(
-                    Entity::Plan,
-                    Some("CountAsterisk can't be present in Selection filter!".into()),
-                ))
-            }
-        };
-
-        Ok(result)
-    }
-}
 
 impl Display for BinaryOp {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -689,23 +617,6 @@ impl Display for BinaryOp {
     }
 }
 
-impl Display for Selection {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        let s = match &self {
-            Selection::Row(r) => r.to_string(),
-            Selection::BinaryOp { left, op, right } => {
-                format!("{left} {op} {right}")
-            }
-            Selection::UnaryOp { op, child } => match op {
-                Unary::IsNull | Unary::IsNotNull => format!("{child} {op}"),
-                Unary::Exists | Unary::NotExists => format!("{op} {child}"),
-            },
-        };
-
-        write!(f, "{s}")
-    }
-}
-
 #[derive(Debug, Serialize)]
 struct SubQuery {
     /// Subquery alias. For subquery in `WHERE` cause alias is `None`.
@@ -803,7 +714,7 @@ impl Display for Target {
 
 #[derive(Debug, Serialize)]
 struct InnerJoin {
-    condition: Selection,
+    condition: ColExpr,
     kind: JoinKind,
 }
 
@@ -828,13 +739,13 @@ enum ExplainNode {
     Except,
     GroupBy(GroupBy),
     InnerJoin(InnerJoin),
-    ValueRow(Row),
+    ValueRow(ColExpr),
     Value,
     Insert(String, ConflictStrategy),
     Projection(Projection),
     Scan(Scan),
-    Selection(Selection),
-    Having(Selection),
+    Selection(ColExpr),
+    Having(ColExpr),
     UnionAll,
     Update(Update),
     SubQuery(SubQuery),
@@ -871,10 +782,8 @@ struct ExplainTreePart {
     /// Level hepls to detect count of idents
     #[serde(skip_serializing)]
     level: usize,
-
     /// Current node of sql query
     current: Option<ExplainNode>,
-
     /// Children nodes of current sql node
     children: Vec<ExplainTreePart>,
 }
@@ -984,7 +893,7 @@ impl FullExplain {
                         )
                     })?;
                     current_node.children.push(child);
-                    let p = GroupBy::new(ir, gr_cols, *output)?;
+                    let p = GroupBy::new(ir, gr_cols, *output, &HashMap::new())?;
                     Some(ExplainNode::GroupBy(p))
                 }
                 Relational::Projection { output, .. } => {
@@ -995,7 +904,7 @@ impl FullExplain {
                         )
                     })?;
                     current_node.children.push(child);
-                    let p = Projection::new(ir, *output)?;
+                    let p = Projection::new(ir, *output, &HashMap::new())?;
                     Some(ExplainNode::Projection(p))
                 }
                 Relational::ScanRelation {
@@ -1013,8 +922,7 @@ impl FullExplain {
                 | Relational::Having {
                     children, filter, ..
                 } => {
-                    let mut sq_ref_map: HashMap<usize, usize> =
-                        HashMap::with_capacity(children.len() - 1);
+                    let mut sq_ref_map: SubQueryRefMap = HashMap::with_capacity(children.len() - 1);
                     if let Some((_, other)) = children.split_first() {
                         for sq_id in other.iter().rev() {
                             let sq_node = stack.pop().ok_or_else(|| {
@@ -1038,10 +946,10 @@ impl FullExplain {
                         ));
                     }
                     let filter_id = ir.undo.get_oldest(filter).map_or_else(|| *filter, |id| *id);
-                    let s = Selection::new(ir, filter_id, &sq_ref_map)?;
+                    let selection = ColExpr::new(ir, filter_id, &sq_ref_map)?;
                     let explain_node = match &node {
-                        Relational::Selection { .. } => ExplainNode::Selection(s),
-                        Relational::Having { .. } => ExplainNode::Having(s),
+                        Relational::Selection { .. } => ExplainNode::Selection(selection),
+                        Relational::Having { .. } => ExplainNode::Having(selection),
                         _ => return Err(SbroadError::DoSkip),
                     };
                     Some(explain_node)
@@ -1139,8 +1047,7 @@ impl FullExplain {
                         ));
                     }
                     let (_, subquery_ids) = children.split_at(2);
-                    let mut sq_ref_map: HashMap<usize, usize> =
-                        HashMap::with_capacity(children.len() - 2);
+                    let mut sq_ref_map: SubQueryRefMap = HashMap::with_capacity(children.len() - 2);
 
                     for sq_id in subquery_ids.iter().rev() {
                         let sq_node = stack.pop().ok_or_else(|| {
@@ -1162,15 +1069,14 @@ impl FullExplain {
                         ));
                     }
 
-                    let condition = Selection::new(ir, *condition, &sq_ref_map)?;
+                    let condition = ColExpr::new(ir, *condition, &sq_ref_map)?;
                     Some(ExplainNode::InnerJoin(InnerJoin {
                         condition,
                         kind: kind.clone(),
                     }))
                 }
                 Relational::ValuesRow { data, children, .. } => {
-                    let mut sq_ref_map: HashMap<usize, usize> =
-                        HashMap::with_capacity(children.len());
+                    let mut sq_ref_map: SubQueryRefMap = HashMap::with_capacity(children.len());
 
                     for sq_id in children.iter().rev() {
                         let sq_node = stack.pop().ok_or_else(|| {
@@ -1184,8 +1090,7 @@ impl FullExplain {
                         sq_ref_map.insert(*sq_id, offset);
                     }
 
-                    let values = ir.get_expression_node(*data)?.get_row_list()?;
-                    let row = Row::from_ir_nodes(ir, values, &sq_ref_map)?;
+                    let row = ColExpr::new(ir, *data, &sq_ref_map)?;
 
                     Some(ExplainNode::ValueRow(row))
                 }
diff --git a/sbroad-core/src/ir/explain/tests.rs b/sbroad-core/src/ir/explain/tests.rs
index 44aecc556e..4489affa7f 100644
--- a/sbroad-core/src/ir/explain/tests.rs
+++ b/sbroad-core/src/ir/explain/tests.rs
@@ -208,7 +208,7 @@ WHERE "id" IN (SELECT "id"
 
     let mut actual_explain = String::new();
     actual_explain.push_str(r#"projection ("t"."id"::unsigned -> "id", "t"."FIRST_NAME"::string -> "FIRST_NAME")
-    selection ROW("t"."id"::unsigned) in ROW($0) or ROW("t"."id"::unsigned) in ROW($1)
+    selection (ROW("t"."id"::unsigned) in ROW($0) or ROW("t"."id"::unsigned) in ROW($1))
         scan "t"
             union all
                 projection ("test_space"."id"::unsigned -> "id", "test_space"."FIRST_NAME"::string -> "FIRST_NAME")
diff --git a/sbroad-core/src/ir/explain/tests/concat.rs b/sbroad-core/src/ir/explain/tests/concat.rs
index 5d561dbf8f..f46fedb427 100644
--- a/sbroad-core/src/ir/explain/tests/concat.rs
+++ b/sbroad-core/src/ir/explain/tests/concat.rs
@@ -6,7 +6,7 @@ fn concat1_test() {
         r#"SELECT CAST('1' as string) || 'hello' FROM "t1""#,
         &format!(
             "{}\n{}\n{}\n{}\n{}\n",
-            r#"projection (('1'::string::string) || ('hello'::string) -> "COL_1")"#,
+            r#"projection (ROW('1'::string::string) || ROW('hello'::string) -> "COL_1")"#,
             r#"    scan "t1""#,
             r#"execution options:"#,
             r#"sql_vdbe_max_steps = 45000"#,
@@ -22,7 +22,7 @@ fn concat2_test() {
         &format!(
             "{}\n{}\n{}\n{}\n{}\n{}\n",
             r#"projection ("t1"."a"::string -> "a")"#,
-            r#"    selection ROW(('1'::string::string) || (("FUNC"(('hello'::string))::integer) || ('2'::string))) = ROW(42::unsigned)"#,
+            r#"    selection ROW(ROW('1'::string::string) || ROW(ROW("FUNC"(('hello'::string))::integer) || ROW('2'::string))) = ROW(42::unsigned)"#,
             r#"        scan "t1""#,
             r#"execution options:"#,
             r#"sql_vdbe_max_steps = 45000"#,
-- 
GitLab