From 83e269eb3884800c4679da9704e80075b0fa74ee Mon Sep 17 00:00:00 2001
From: Andrey Strochuk <a.strochuk@picodata.io>
Date: Tue, 20 Aug 2024 01:09:00 +0300
Subject: [PATCH] feat: add multiple subarenas

---
 sbroad-benches/benches/parse.rs               |    3 +-
 sbroad-cartridge/src/cartridge/router.rs      |    2 +-
 sbroad-cartridge/src/cartridge/storage.rs     |    2 +-
 .../test/integration/arithmetic_test.lua      |    7 +-
 sbroad-core/src/backend/sql/ir.rs             |   54 +-
 .../src/backend/sql/ir/tests/except.rs        |   11 +
 .../src/backend/sql/ir/tests/selection.rs     |    4 +-
 sbroad-core/src/backend/sql/space.rs          |    9 +-
 sbroad-core/src/backend/sql/tree.rs           |  140 +-
 sbroad-core/src/cbo/histogram.rs              |    6 +-
 sbroad-core/src/errors.rs                     |   13 +-
 sbroad-core/src/executor.rs                   |    8 +-
 sbroad-core/src/executor/bucket.rs            |   82 +-
 sbroad-core/src/executor/engine.rs            |    2 +-
 sbroad-core/src/executor/engine/helpers.rs    |   94 +-
 .../src/executor/engine/helpers/storage.rs    |    2 +-
 .../src/executor/engine/helpers/vshard.rs     |   28 +-
 sbroad-core/src/executor/engine/mock.rs       |    2 +-
 sbroad-core/src/executor/ir.rs                |  176 ++-
 sbroad-core/src/executor/protocol.rs          |    2 +-
 sbroad-core/src/executor/result.rs            |    8 +-
 sbroad-core/src/executor/tests.rs             |   46 +-
 sbroad-core/src/executor/tests/between.rs     |    6 +-
 .../src/executor/tests/empty_motion.rs        |    4 +-
 sbroad-core/src/executor/tests/exec_plan.rs   |  141 +-
 sbroad-core/src/executor/tests/not_eq.rs      |    2 +-
 sbroad-core/src/executor/tests/not_in.rs      |    2 +-
 sbroad-core/src/executor/vtable.rs            |    8 +-
 sbroad-core/src/frontend/sql.rs               |  197 +--
 sbroad-core/src/frontend/sql/ir.rs            |  285 ++--
 sbroad-core/src/frontend/sql/ir/tests.rs      |  366 ++---
 sbroad-core/src/frontend/sql/ir/tests/ddl.rs  |   20 +-
 .../src/frontend/sql/ir/tests/global.rs       |   67 +-
 .../src/frontend/sql/ir/tests/limit.rs        |   14 +-
 .../src/frontend/sql/ir/tests/single.rs       |    8 +-
 sbroad-core/src/ir.rs                         |  895 +++++++----
 sbroad-core/src/ir/acl.rs                     |  100 +-
 sbroad-core/src/ir/aggregates.rs              |   17 +-
 sbroad-core/src/ir/api/children.rs            |   12 +-
 sbroad-core/src/ir/api/constant.rs            |   58 +-
 sbroad-core/src/ir/api/parameter.rs           |  165 ++-
 sbroad-core/src/ir/block.rs                   |   48 +-
 sbroad-core/src/ir/ddl.rs                     |  141 +-
 sbroad-core/src/ir/distribution.rs            |   72 +-
 sbroad-core/src/ir/distribution/tests.rs      |   40 +-
 sbroad-core/src/ir/explain.rs                 |  129 +-
 sbroad-core/src/ir/expression.rs              |  592 ++------
 sbroad-core/src/ir/expression/cast.rs         |   14 +-
 sbroad-core/src/ir/expression/concat.rs       |   15 +-
 sbroad-core/src/ir/expression/types.rs        |   52 +-
 sbroad-core/src/ir/function.rs                |   14 +-
 sbroad-core/src/ir/helpers.rs                 |  173 +--
 sbroad-core/src/ir/helpers/tests.rs           |  292 ++--
 sbroad-core/src/ir/node.rs                    | 1310 +++++++++++++++++
 sbroad-core/src/ir/node/acl.rs                |  121 ++
 sbroad-core/src/ir/node/block.rs              |   50 +
 sbroad-core/src/ir/node/ddl.rs                |  134 ++
 sbroad-core/src/ir/node/expression.rs         |  263 ++++
 sbroad-core/src/ir/node/relational.rs         |  704 +++++++++
 sbroad-core/src/ir/node/util.rs               |   14 +
 sbroad-core/src/ir/operator.rs                |  956 ++++--------
 sbroad-core/src/ir/operator/tests.rs          |   34 +-
 sbroad-core/src/ir/parameters.rs              |   12 +-
 sbroad-core/src/ir/tests.rs                   |    6 +-
 sbroad-core/src/ir/transformation.rs          |   78 +-
 sbroad-core/src/ir/transformation/bool_in.rs  |    7 +-
 sbroad-core/src/ir/transformation/dnf.rs      |   23 +-
 .../ir/transformation/equality_propagation.rs |   29 +-
 .../equality_propagation/tests.rs             |    8 +-
 .../src/ir/transformation/merge_tuples.rs     |   41 +-
 .../src/ir/transformation/not_push_down.rs    |   34 +-
 .../src/ir/transformation/redistribution.rs   |  173 +--
 .../ir/transformation/redistribution/dml.rs   |   37 +-
 .../transformation/redistribution/eq_cols.rs  |   66 +-
 .../transformation/redistribution/groupby.rs  |  203 +--
 .../redistribution/left_join.rs               |    7 +-
 .../ir/transformation/redistribution/tests.rs |   12 +-
 .../redistribution/tests/between.rs           |    7 +-
 .../redistribution/tests/except.rs            |   11 +-
 .../redistribution/tests/not_in.rs            |   15 +-
 .../redistribution/tests/segment.rs           |   43 +-
 .../src/ir/transformation/split_columns.rs    |   19 +-
 .../ir/transformation/split_columns/tests.rs  |    4 +-
 sbroad-core/src/ir/tree.rs                    |   63 +-
 sbroad-core/src/ir/tree/and.rs                |    7 +-
 sbroad-core/src/ir/tree/expression.rs         |   40 +-
 sbroad-core/src/ir/tree/relation.rs           |   29 +-
 sbroad-core/src/ir/tree/subtree.rs            |  152 +-
 sbroad-core/src/ir/tree/tests.rs              |   10 +-
 sbroad-core/src/ir/undo.rs                    |    2 +-
 90 files changed, 5813 insertions(+), 3561 deletions(-)
 create mode 100644 sbroad-core/src/ir/node.rs
 create mode 100644 sbroad-core/src/ir/node/acl.rs
 create mode 100644 sbroad-core/src/ir/node/block.rs
 create mode 100644 sbroad-core/src/ir/node/ddl.rs
 create mode 100644 sbroad-core/src/ir/node/expression.rs
 create mode 100644 sbroad-core/src/ir/node/relational.rs
 create mode 100644 sbroad-core/src/ir/node/util.rs

diff --git a/sbroad-benches/benches/parse.rs b/sbroad-benches/benches/parse.rs
index 71c49f950..c875a7a5e 100644
--- a/sbroad-benches/benches/parse.rs
+++ b/sbroad-benches/benches/parse.rs
@@ -420,7 +420,6 @@ criterion_group!(
     benches,
     bench_pure_pest_parsing,
     bench_full_parsing,
-    bench_serde_clone,
-    bench_take_subtree,
+    bench_serde_clone
 );
 criterion_main!(benches);
diff --git a/sbroad-cartridge/src/cartridge/router.rs b/sbroad-cartridge/src/cartridge/router.rs
index a5de1d2f6..33234893c 100644
--- a/sbroad-cartridge/src/cartridge/router.rs
+++ b/sbroad-cartridge/src/cartridge/router.rs
@@ -3,7 +3,7 @@
 use sbroad::cbo::{ColumnStats, TableColumnPair, TableStats};
 use sbroad::executor::engine::helpers::vshard::{get_random_bucket, impl_exec_ir_on_buckets};
 use sbroad::executor::engine::{DispatchReturnFormat, Metadata, QueryCache, Vshard};
-use sbroad::ir::expression::NodeId;
+use sbroad::ir::node::NodeId;
 use sbroad::utils::MutexLike;
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 use tarantool::fiber::Mutex;
diff --git a/sbroad-cartridge/src/cartridge/storage.rs b/sbroad-cartridge/src/cartridge/storage.rs
index 50c55146a..99b244129 100644
--- a/sbroad-cartridge/src/cartridge/storage.rs
+++ b/sbroad-cartridge/src/cartridge/storage.rs
@@ -15,8 +15,8 @@ use sbroad::executor::hash::bucket_id_by_tuple;
 use sbroad::executor::ir::{ExecutionPlan, QueryType};
 use sbroad::executor::lru::{Cache, LRUCache, DEFAULT_CAPACITY};
 use sbroad::executor::protocol::{RequiredData, SchemaInfo};
+use sbroad::ir::node::NodeId;
 use sbroad::ir::value::Value;
-use sbroad::ir::expression::NodeId;
 use sbroad::utils::MutexLike;
 use sbroad::{debug, error, warn};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
diff --git a/sbroad-cartridge/test_app/test/integration/arithmetic_test.lua b/sbroad-cartridge/test_app/test/integration/arithmetic_test.lua
index d6de46268..b88fc3bff 100644
--- a/sbroad-cartridge/test_app/test/integration/arithmetic_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/arithmetic_test.lua
@@ -924,9 +924,9 @@ g2.test_alias = function()
         {name = "a", type = "integer"},
     })
 
-    local res, err = api:call("sbroad.execute", { [[
-        select "id", "id" + "a" as "sum", "id" * "a" as "mul", "a" from "arithmetic_space"
-        ]], {}})
+    local res, err = api:call("sbroad.execute", {
+        [[select "id", "id" + "a" as "sum", "id" * "a" as "mul", "a" from "arithmetic_space"]], {}
+    })
     t.assert_equals(err, nil)
     t.assert_equals(res.metadata, {
         {name = "id", type = "integer"},
@@ -934,6 +934,7 @@ g2.test_alias = function()
         {name = "mul", type = "integer"},
         {name = "a", type = "integer"},
     })
+
 end
 
 g2.test_associativity = function()
diff --git a/sbroad-core/src/backend/sql/ir.rs b/sbroad-core/src/backend/sql/ir.rs
index b033e3f71..44ff43d87 100644
--- a/sbroad-core/src/backend/sql/ir.rs
+++ b/sbroad-core/src/backend/sql/ir.rs
@@ -1,5 +1,10 @@
 use crate::debug;
 use crate::executor::protocol::VTablesMeta;
+use crate::ir::node::expression::Expression;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{
+    Constant, Insert, Join, Motion, Node, NodeId, Reference, ScanRelation, StableFunction,
+};
 use crate::ir::relation::Column;
 use crate::ir::transformation::redistribution::MotionPolicy;
 use crate::ir::tree::traversal::{LevelNode, PostOrderWithFilter};
@@ -14,10 +19,8 @@ use tarantool::tuple::{FunctionArgs, Tuple};
 use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::engine::helpers::table_name;
 use crate::executor::ir::ExecutionPlan;
-use crate::ir::expression::{Expression, NodeId};
-use crate::ir::operator::{OrderByType, Relational};
+use crate::ir::operator::OrderByType;
 use crate::ir::value::{LuaValue, Value};
-use crate::ir::Node;
 use crate::otm::{child_span, current_id, deserialize_context, inject_context, query_id};
 
 use super::space::{create_table, TableGuard};
@@ -173,7 +176,8 @@ impl ExecutionPlan {
         let nodes = tree.take_nodes();
         let mut params: Vec<Value> = Vec::with_capacity(nodes.len());
         for LevelNode(_, param_id) in nodes {
-            let Expression::Constant { value } = plan.get_expression_node(param_id)? else {
+            let Expression::Constant(Constant { value }) = plan.get_expression_node(param_id)?
+            else {
                 return Err(SbroadError::Invalid(
                     Entity::Plan,
                     Some(format_smolstr!(
@@ -209,10 +213,10 @@ impl ExecutionPlan {
                 let motion = ir.get_relation_node(motion_id)?;
                 if !matches!(
                     motion,
-                    Relational::Motion {
+                    Relational::Motion(Motion {
                         policy: MotionPolicy::LocalSegment { .. } | MotionPolicy::Local,
                         ..
-                    }
+                    })
                 ) {
                     reserved_motion_id = Some(motion_id);
                 }
@@ -369,6 +373,15 @@ impl ExecutionPlan {
                                     ),
                                 ));
                             }
+                            Node::Invalid(..) => {
+                                return Err(SbroadError::Unsupported(
+                                    Entity::Node,
+                                    Some(
+                                        "Invalid nodes are not supported in the generated SQL"
+                                            .into(),
+                                    ),
+                                ));
+                            }
                             Node::Relational(rel) => match rel {
                                 Relational::Except { .. } => sql.push_str("EXCEPT"),
                                 Relational::GroupBy { .. } => sql.push_str("GROUP BY"),
@@ -384,15 +397,15 @@ impl ExecutionPlan {
                                         ),
                                     ));
                                 }
-                                Relational::Insert { relation, .. } => {
+                                Relational::Insert(Insert { relation, .. }) => {
                                     sql.push_str("INSERT INTO ");
                                     push_identifier(&mut sql, relation);
                                 }
-                                Relational::Join { kind, .. } => sql.push_str(
+                                Relational::Join(Join { kind, .. }) => sql.push_str(
                                     format!("{} JOIN", kind.to_string().to_uppercase()).as_str(),
                                 ),
                                 Relational::Projection { .. } => sql.push_str("SELECT"),
-                                Relational::ScanRelation { relation, .. } => {
+                                Relational::ScanRelation(ScanRelation { relation, .. }) => {
                                     push_identifier(&mut sql, relation);
                                 }
                                 Relational::ScanSubQuery { .. }
@@ -418,7 +431,7 @@ impl ExecutionPlan {
                                     | Expression::Row { .. }
                                     | Expression::Trim { .. }
                                     | Expression::Unary { .. } => {}
-                                    Expression::Constant { value, .. } => {
+                                    Expression::Constant(Constant { value, .. }) => {
                                         write!(sql, "{value}").map_err(|e| {
                                             SbroadError::FailedTo(
                                                 Action::Put,
@@ -427,7 +440,7 @@ impl ExecutionPlan {
                                             )
                                         })?;
                                     }
-                                    Expression::Reference { position, .. } => {
+                                    Expression::Reference(Reference { position, .. }) => {
                                         let rel_id =
                                             *ir_plan.get_relational_from_reference_node(*id)?;
                                         let rel_node = ir_plan.get_relation_node(rel_id)?;
@@ -455,16 +468,15 @@ impl ExecutionPlan {
                                             }
                                         }
 
-                                        let alias = &ir_plan.get_alias_from_reference_node(expr)?;
+                                        let alias =
+                                            &ir_plan.get_alias_from_reference_node(&expr)?;
                                         if rel_node.is_insert() {
                                             // We expect `INSERT INTO t(a, b) VALUES(1, 2)`
                                             // rather then `INSERT INTO t(t.a, t.b) VALUES(1, 2)`.
                                             push_identifier(&mut sql, alias);
                                             continue;
                                         }
-                                        if let Some(name) =
-                                            rel_node.scan_name(ir_plan, *position)?
-                                        {
+                                        if let Some(name) = ir_plan.scan_name(rel_id, *position)? {
                                             push_identifier(&mut sql, name);
                                             sql.push('.');
                                             push_identifier(&mut sql, alias);
@@ -473,16 +485,18 @@ impl ExecutionPlan {
                                         }
                                         push_identifier(&mut sql, alias);
                                     }
-                                    Expression::StableFunction {
-                                        name, is_system, ..
-                                    } => {
+                                    Expression::StableFunction(StableFunction {
+                                        name,
+                                        is_system,
+                                        ..
+                                    }) => {
                                         if *is_system {
                                             sql.push_str(name);
                                         } else {
                                             push_identifier(&mut sql, name);
                                         }
                                     }
-                                    Expression::CountAsterisk => {
+                                    Expression::CountAsterisk { .. } => {
                                         sql.push('*');
                                     }
                                 }
@@ -492,7 +506,7 @@ impl ExecutionPlan {
                     SyntaxData::Parameter(id) => {
                         sql.push('?');
                         let value = ir_plan.get_expression_node(*id)?;
-                        if let Expression::Constant { value, .. } = value {
+                        if let Expression::Constant(Constant { value, .. }) = value {
                             params.push(value.clone());
                         } else {
                             return Err(SbroadError::Invalid(
diff --git a/sbroad-core/src/backend/sql/ir/tests/except.rs b/sbroad-core/src/backend/sql/ir/tests/except.rs
index 2180d36f1..83fffff38 100644
--- a/sbroad-core/src/backend/sql/ir/tests/except.rs
+++ b/sbroad-core/src/backend/sql/ir/tests/except.rs
@@ -1,4 +1,5 @@
 use super::*;
+use crate::ir::node::*;
 use crate::ir::tree::Snapshot;
 use crate::ir::value::Value;
 
@@ -49,3 +50,13 @@ fn except1_oldest() {
     );
     check_sql_with_snapshot(query, vec![], expected, Snapshot::Oldest);
 }
+
+// TODO create test for size equals than 40(32) for 64, 96, 136
+#[test]
+fn test_node_size() {
+    assert!(std::mem::size_of::<Node32>() == 40);
+    assert!(std::mem::size_of::<Node64>() == 72);
+    assert!(std::mem::size_of::<Node96>() == 96);
+    assert!(std::mem::size_of::<Node136>() == 136);
+    assert!(std::mem::size_of::<Node224>() == 224);
+}
diff --git a/sbroad-core/src/backend/sql/ir/tests/selection.rs b/sbroad-core/src/backend/sql/ir/tests/selection.rs
index 7e1b97e2c..20080afa2 100644
--- a/sbroad-core/src/backend/sql/ir/tests/selection.rs
+++ b/sbroad-core/src/backend/sql/ir/tests/selection.rs
@@ -67,9 +67,9 @@ fn selection2_latest() {
     let expected = PatternWithParams::new(
         [
             r#"SELECT "hash_testing"."product_code" FROM "hash_testing""#,
-            r#"WHERE ("hash_testing"."product_units", "hash_testing"."product_units", "hash_testing"."identification_number") = ("hash_testing"."identification_number", ?, ?)"#,
+            r#"WHERE ("hash_testing"."identification_number", "hash_testing"."product_units", "hash_testing"."identification_number") = ("hash_testing"."product_units", ?, ?)"#,
             r#"and ("hash_testing"."product_units") <> ("hash_testing"."sys_op")"#,
-            r#"or ("hash_testing"."product_units", "hash_testing"."product_units", "hash_testing"."identification_number") = ("hash_testing"."identification_number", ?, ?)"#,
+            r#"or ("hash_testing"."identification_number", "hash_testing"."product_units", "hash_testing"."identification_number") = ("hash_testing"."product_units", ?, ?)"#,
             r#"and ("hash_testing"."product_units") is null"#
         ].join(" "),
         vec![Value::Unsigned(1), Value::Unsigned(1), Value::Unsigned(1), Value::Unsigned(1)],
diff --git a/sbroad-core/src/backend/sql/space.rs b/sbroad-core/src/backend/sql/space.rs
index 4807b384b..d6c40c502 100644
--- a/sbroad-core/src/backend/sql/space.rs
+++ b/sbroad-core/src/backend/sql/space.rs
@@ -1,7 +1,9 @@
+use std::fmt::Debug;
+
 use crate::executor::ir::ExecutionPlan;
-use crate::ir::expression::NodeId;
+use crate::executor::protocol::VTablesMeta;
 use crate::ir::relation::SpaceEngine;
-use crate::{errors::SbroadError, executor::protocol::VTablesMeta};
+use crate::{errors::SbroadError, ir::node::NodeId};
 
 #[cfg(not(feature = "mock"))]
 mod prod_imports {
@@ -11,6 +13,7 @@ mod prod_imports {
     pub use crate::executor::engine::helpers::{pk_name, table_name};
     pub use crate::executor::ir::ExecutionPlan;
     use crate::executor::protocol::VTablesMeta;
+    pub use crate::ir::node::NodeId;
     pub use crate::ir::relation::SpaceEngine;
     pub use smol_str::{format_smolstr, SmolStr};
     pub use tarantool::index::{FieldType, IndexOptions, IndexType, Part};
@@ -20,7 +23,7 @@ mod prod_imports {
     pub fn create_tmp_space_impl(
         exec_plan: &ExecutionPlan,
         plan_id: &str,
-        motion_id: usize,
+        motion_id: NodeId,
         engine: &SpaceEngine,
         vtables_meta: Option<&VTablesMeta>,
     ) -> Result<SmolStr, SbroadError> {
diff --git a/sbroad-core/src/backend/sql/tree.rs b/sbroad-core/src/backend/sql/tree.rs
index 51954121c..d1ac606c1 100644
--- a/sbroad-core/src/backend/sql/tree.rs
+++ b/sbroad-core/src/backend/sql/tree.rs
@@ -4,12 +4,20 @@ use std::mem::take;
 
 use crate::errors::{Entity, SbroadError};
 use crate::executor::ir::ExecutionPlan;
-use crate::ir::expression::{Expression, FunctionFeature, NodeId, TrimKind};
-use crate::ir::operator::{Bool, OrderByElement, OrderByEntity, OrderByType, Relational, Unary};
+use crate::ir::expression::{FunctionFeature, TrimKind};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Except, ExprInParentheses, GroupBy,
+    Having, Intersect, Join, Limit, Motion, Node, NodeId, OrderBy, Projection, Reference, Row,
+    ScanCte, ScanRelation, ScanSubQuery, Selection, StableFunction, Trim, UnaryExpr, Union,
+    UnionAll, Values, ValuesRow,
+};
+use crate::ir::operator::{Bool, OrderByElement, OrderByEntity, OrderByType, Unary};
 use crate::ir::transformation::redistribution::{MotionOpcode, MotionPolicy};
 use crate::ir::tree::traversal::{LevelNode, PostOrder};
 use crate::ir::tree::Snapshot;
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 
@@ -479,7 +487,7 @@ impl Select {
         id: usize,
     ) -> Result<Option<Select>, SbroadError> {
         let sn = sp.nodes.get_sn(id);
-        if let Some(Node::Relational(Relational::Projection { .. })) = sp.get_plan_node(&sn.data)? {
+        if let Some(Node::Relational(Relational::Projection(_))) = sp.get_plan_node(&sn.data)? {
             let mut select = Select {
                 parent,
                 branch,
@@ -495,10 +503,10 @@ impl Select {
                 let sn_left = sp.nodes.get_sn(left_id);
                 let plan_node_left = sp.plan_node_or_err(&sn_left.data)?;
                 if let Node::Relational(
-                    Relational::ScanRelation { .. }
-                    | Relational::ScanCte { .. }
-                    | Relational::ScanSubQuery { .. }
-                    | Relational::Motion { .. },
+                    Relational::ScanRelation(_)
+                    | Relational::ScanCte(_)
+                    | Relational::ScanSubQuery(_)
+                    | Relational::Motion(_),
                 ) = plan_node_left
                 {
                     select.scan = left_id;
@@ -564,13 +572,13 @@ impl<'p> SyntaxPlan<'p> {
             let plan = self.plan.get_ir_plan();
             let node = plan.get_node(plan_id).expect("node in the plan must exist");
             match node {
-                Node::Relational(Relational::Motion { children, .. }) => {
+                Node::Relational(Relational::Motion(Motion { children, .. })) => {
                     let child_id = *children.first().expect("MOTION child must exist");
                     if *id == child_id {
                         return;
                     }
                 }
-                Node::Expression(Expression::Row { .. }) => {
+                Node::Expression(Expression::Row(_)) => {
                     let rel_ids = plan
                         .get_relational_nodes_from_row(plan_id)
                         .expect("row relational nodes");
@@ -618,11 +626,11 @@ impl<'p> SyntaxPlan<'p> {
             Node::Ddl(..) => panic!("DDL node {node:?} is not supported in the syntax plan"),
             Node::Acl(..) => panic!("ACL node {node:?} is not supported in the syntax plan"),
             Node::Block(..) => panic!("Block node {node:?} is not supported in the syntax plan"),
-            Node::Parameter(..) => {
+            Node::Invalid(..) | Node::Parameter(..) => {
                 let sn = SyntaxNode::new_parameter(id);
                 self.nodes.push_sn_plan(sn);
             }
-            Node::Relational(rel) => match rel {
+            Node::Relational(ref rel) => match rel {
                 Relational::Insert { .. }
                 | Relational::Delete { .. }
                 | Relational::Update { .. } => {
@@ -654,7 +662,7 @@ impl<'p> SyntaxPlan<'p> {
                     let sn = SyntaxNode::new_parameter(id);
                     self.nodes.push_sn_plan(sn);
                 }
-                Expression::Reference { .. } | Expression::CountAsterisk => {
+                Expression::Reference { .. } | Expression::CountAsterisk { .. } => {
                     let sn = SyntaxNode::new_pointer(id, None, vec![]);
                     self.nodes.push_sn_plan(sn);
                 }
@@ -668,7 +676,7 @@ impl<'p> SyntaxPlan<'p> {
         }
     }
 
-    fn prologue_rel(&self, id: NodeId) -> (&Plan, &Relational) {
+    fn prologue_rel(&self, id: NodeId) -> (&Plan, Relational) {
         let plan = self.plan.get_ir_plan();
         let rel = plan
             .get_relation_node(id)
@@ -676,7 +684,7 @@ impl<'p> SyntaxPlan<'p> {
         (plan, rel)
     }
 
-    fn prologue_expr(&self, id: NodeId) -> (&Plan, &Expression) {
+    fn prologue_expr(&self, id: NodeId) -> (&Plan, Expression) {
         let plan = self.plan.get_ir_plan();
         let expr = plan
             .get_expression_node(id)
@@ -688,7 +696,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_cte(&mut self, id: NodeId) {
         let (_, cte) = self.prologue_rel(id);
-        let Relational::ScanCte { alias, child, .. } = cte else {
+        let Relational::ScanCte(ScanCte { alias, child, .. }) = cte else {
             panic!("expected CTE node");
         };
         let (child, alias) = (*child, alias.clone());
@@ -706,12 +714,12 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_filter(&mut self, id: NodeId) {
         let (plan, rel) = self.prologue_rel(id);
-        let (Relational::Selection {
+        let (Relational::Selection(Selection {
             children, filter, ..
-        }
-        | Relational::Having {
+        })
+        | Relational::Having(Having {
             children, filter, ..
-        }) = rel
+        })) = rel
         else {
             panic!("Expected FILTER node");
         };
@@ -728,9 +736,9 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_group_by(&mut self, id: NodeId) {
         let (_, gb) = self.prologue_rel(id);
-        let Relational::GroupBy {
+        let Relational::GroupBy(GroupBy {
             children, gr_cols, ..
-        } = gb
+        }) = gb
         else {
             panic!("Expected GROUP BY node");
         };
@@ -759,11 +767,11 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_join(&mut self, id: NodeId) {
         let (plan, join) = self.prologue_rel(id);
-        let Relational::Join {
+        let Relational::Join(Join {
             children,
             condition,
             ..
-        } = join
+        }) = join
         else {
             panic!("Expected JOIN node");
         };
@@ -794,14 +802,14 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_motion(&mut self, id: NodeId) {
         let (plan, motion) = self.prologue_rel(id);
-        let Relational::Motion {
+        let Relational::Motion(Motion {
             policy,
             children,
             is_child_subquery,
             program,
             output,
             ..
-        } = motion
+        }) = motion
         else {
             panic!("Expected MOTION node");
         };
@@ -841,7 +849,7 @@ impl<'p> SyntaxPlan<'p> {
                         .get_child_under_alias(*col)
                         .expect("motion output must be a row of aliases!");
                     let ref_expr = plan.get_expression_node(ref_id).expect("reference node");
-                    let Expression::Reference { col_type, .. } = ref_expr else {
+                    let Expression::Reference(Reference { col_type, .. }) = ref_expr else {
                         panic!("expected Reference under Alias in Motion output");
                     };
                     select_columns.push(format_smolstr!("cast(null as {col_type})"));
@@ -899,11 +907,11 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_order_by(&mut self, id: NodeId) {
         let (_, order_by) = self.prologue_rel(id);
-        let Relational::OrderBy {
+        let Relational::OrderBy(OrderBy {
             order_by_elements,
             child,
             ..
-        } = order_by
+        }) = order_by
         else {
             panic!("expect ORDER BY node");
         };
@@ -955,9 +963,9 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_proj(&mut self, id: NodeId) {
         let (_, proj) = self.prologue_rel(id);
-        let Relational::Projection {
+        let Relational::Projection(Projection {
             children, output, ..
-        } = proj
+        }) = proj
         else {
             panic!("Expected PROJECTION node");
         };
@@ -988,7 +996,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_scan_relation(&mut self, id: NodeId) {
         let (_, scan) = self.prologue_rel(id);
-        let Relational::ScanRelation { alias, .. } = scan else {
+        let Relational::ScanRelation(ScanRelation { alias, .. }) = scan else {
             panic!("Expected SCAN node");
         };
         let scan_alias = alias.clone();
@@ -1004,10 +1012,10 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_set(&mut self, id: NodeId) {
         let (_, set) = self.prologue_rel(id);
-        let (Relational::Except { left, right, .. }
-        | Relational::Union { left, right, .. }
-        | Relational::UnionAll { left, right, .. }
-        | Relational::Intersect { left, right, .. }) = set
+        let (Relational::Except(Except { left, right, .. })
+        | Relational::Union(Union { left, right, .. })
+        | Relational::UnionAll(UnionAll { left, right, .. })
+        | Relational::Intersect(Intersect { left, right, .. })) = set
         else {
             panic!("Expected SET node");
         };
@@ -1020,9 +1028,9 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_sq(&mut self, id: NodeId) {
         let (_, sq) = self.prologue_rel(id);
-        let Relational::ScanSubQuery {
+        let Relational::ScanSubQuery(ScanSubQuery {
             children, alias, ..
-        } = sq
+        }) = sq
         else {
             panic!("Expected SUBQUERY node");
         };
@@ -1045,7 +1053,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_values_row(&mut self, id: NodeId) {
         let (_, row) = self.prologue_rel(id);
-        let Relational::ValuesRow { data, .. } = row else {
+        let Relational::ValuesRow(ValuesRow { data, .. }) = row else {
             panic!("Expected VALUES ROW node");
         };
         let data_sn_id = self.pop_from_stack(*data);
@@ -1055,9 +1063,9 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_values(&mut self, id: NodeId) {
         let (_, values) = self.prologue_rel(id);
-        let Relational::Values {
+        let Relational::Values(Values {
             children, output, ..
-        } = values
+        }) = values
         else {
             panic!("Expected VALUES node");
         };
@@ -1091,7 +1099,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_limit(&mut self, id: NodeId) {
         let (_, limit) = self.prologue_rel(id);
-        let Relational::Limit { limit, child, .. } = limit else {
+        let Relational::Limit(Limit { limit, child, .. }) = limit else {
             panic!("expected LIMIT node");
         };
         let (limit, child) = (*limit, *child);
@@ -1109,7 +1117,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_alias(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
-        let Expression::Alias { child, name } = expr else {
+        let Expression::Alias(Alias { child, name }) = expr else {
             panic!("Expected ALIAS node");
         };
         let (child, name) = (*child, name.clone());
@@ -1119,9 +1127,9 @@ impl<'p> SyntaxPlan<'p> {
         let child_expr = plan
             .get_expression_node(child)
             .expect("alias child expression");
-        if let Expression::Reference { .. } = child_expr {
+        if let Expression::Reference(_) = child_expr {
             let alias = plan
-                .get_alias_from_reference_node(child_expr)
+                .get_alias_from_reference_node(&child_expr)
                 .expect("alias name");
             if alias == name {
                 let sn = SyntaxNode::new_pointer(id, None, vec![child_sn_id]);
@@ -1137,18 +1145,18 @@ impl<'p> SyntaxPlan<'p> {
     fn add_binary_op(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
         let (left_plan_id, right_plan_id, op_sn_id) = match expr {
-            Expression::Bool {
+            Expression::Bool(BoolExpr {
                 left, right, op, ..
-            } => {
+            }) => {
                 let (op, left, right) = (op.clone(), *left, *right);
                 let op_sn_id = self
                     .nodes
                     .push_sn_non_plan(SyntaxNode::new_operator(&format!("{op}")));
                 (left, right, op_sn_id)
             }
-            Expression::Arithmetic {
+            Expression::Arithmetic(ArithmeticExpr {
                 left, right, op, ..
-            } => {
+            }) => {
                 let (op, left, right) = (op.clone(), *left, *right);
                 let op_sn_id = self
                     .nodes
@@ -1166,11 +1174,11 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_case(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
-        let Expression::Case {
+        let Expression::Case(Case {
             search_expr,
             when_blocks,
             else_expr,
-        } = expr
+        }) = expr
         else {
             panic!("Expected CASE node");
         };
@@ -1204,7 +1212,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_cast(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
-        let Expression::Cast { child, to } = expr else {
+        let Expression::Cast(Cast { child, to }) = expr else {
             panic!("Expected CAST node");
         };
         let to_alias = SmolStr::from(to);
@@ -1224,7 +1232,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_concat(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
-        let Expression::Concat { left, right } = expr else {
+        let Expression::Concat(Concat { left, right }) = expr else {
             panic!("Expected CONCAT node");
         };
         let (left, right) = (*left, *right);
@@ -1240,7 +1248,7 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_expr_in_parentheses(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
-        let Expression::ExprInParentheses { child } = expr else {
+        let Expression::ExprInParentheses(ExprInParentheses { child }) = expr else {
             panic!("Expected expression in parentheses node");
         };
         let child_sn_id = self.pop_from_stack(*child);
@@ -1259,7 +1267,7 @@ impl<'p> SyntaxPlan<'p> {
         let expr = plan
             .get_expression_node(id)
             .expect("node {id} must exist in the plan");
-        let Expression::Row { list, .. } = expr else {
+        let Expression::Row(Row { list, .. }) = expr else {
             panic!("Expected ROW node");
         };
 
@@ -1284,7 +1292,7 @@ impl<'p> SyntaxPlan<'p> {
             let first_child = plan
                 .get_expression_node(first_child_id)
                 .expect("row child is expression");
-            let first_child_is_ref = matches!(first_child, Expression::Reference { .. });
+            let first_child_is_ref = matches!(first_child, Expression::Reference(_));
 
             // Replace motion node to virtual table node.
             let vtable = self
@@ -1305,7 +1313,7 @@ impl<'p> SyntaxPlan<'p> {
                     let expr = plan
                         .get_expression_node(*child_id)
                         .expect("row child is expression");
-                    if matches!(expr, Expression::Reference { .. }) {
+                    if matches!(expr, Expression::Reference(_)) {
                         let referred_id = *plan
                             .get_relational_from_reference_node(*child_id)
                             .expect("referred id");
@@ -1346,7 +1354,7 @@ impl<'p> SyntaxPlan<'p> {
                     let expr = plan
                         .get_expression_node(*child_id)
                         .expect("row child is expression");
-                    if matches!(expr, Expression::Reference { .. }) {
+                    if matches!(expr, Expression::Reference(_)) {
                         let referred_id = *plan
                             .get_relational_from_reference_node(*child_id)
                             .expect("referred id");
@@ -1383,11 +1391,11 @@ impl<'p> SyntaxPlan<'p> {
         let expr = plan
             .get_expression_node(id)
             .expect("node {id} must exist in the plan");
-        let Expression::StableFunction {
+        let Expression::StableFunction(StableFunction {
             children: args,
             feature,
             ..
-        } = expr
+        }) = expr
         else {
             panic!("Expected stable function node");
         };
@@ -1413,11 +1421,11 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_trim(&mut self, id: NodeId) {
         let (_, expr) = self.prologue_expr(id);
-        let Expression::Trim {
+        let Expression::Trim(Trim {
             kind,
             pattern,
             target,
-        } = expr
+        }) = expr
         else {
             panic!("Expected TRIM node");
         };
@@ -1461,12 +1469,12 @@ impl<'p> SyntaxPlan<'p> {
 
     fn add_unary_op(&mut self, id: NodeId) {
         let (plan, expr) = self.prologue_expr(id);
-        let Expression::Unary { child, op } = expr else {
+        let Expression::Unary(UnaryExpr { child, op }) = expr else {
             panic!("Expected unary expression node");
         };
         let (child, op) = (*child, op.clone());
         let child_node = plan.get_expression_node(child).expect("child expression");
-        let is_and = matches!(child_node, Expression::Bool { op: Bool::And, .. });
+        let is_and = matches!(child_node, Expression::Bool(BoolExpr { op: Bool::And, .. }));
         let operator_node_id = self
             .nodes
             .push_sn_non_plan(SyntaxNode::new_operator(&format!("{op}")));
@@ -1497,7 +1505,7 @@ impl<'p> SyntaxPlan<'p> {
     ///
     /// # Errors
     /// - syntax node wraps an invalid plan node
-    pub fn get_plan_node(&self, data: &SyntaxData) -> Result<Option<&Node>, SbroadError> {
+    pub fn get_plan_node(&self, data: &SyntaxData) -> Result<Option<Node>, SbroadError> {
         if let SyntaxData::PlanId(id) = data {
             Ok(Some(self.plan.get_ir_plan().get_node(*id)?))
         } else {
@@ -1510,7 +1518,7 @@ impl<'p> SyntaxPlan<'p> {
     /// # Errors
     /// - plan node is invalid
     /// - syntax tree node doesn't have a plan node
-    pub fn plan_node_or_err(&self, data: &SyntaxData) -> Result<&Node, SbroadError> {
+    pub fn plan_node_or_err(&self, data: &SyntaxData) -> Result<Node, SbroadError> {
         self.get_plan_node(data)?.ok_or_else(|| {
             SbroadError::Invalid(
                 Entity::SyntaxPlan,
diff --git a/sbroad-core/src/cbo/histogram.rs b/sbroad-core/src/cbo/histogram.rs
index 5d6137a19..6d50610e7 100644
--- a/sbroad-core/src/cbo/histogram.rs
+++ b/sbroad-core/src/cbo/histogram.rs
@@ -197,7 +197,7 @@ pub struct Mcv<T: Scalar> {
     /// Number of such a value divided by the number of all values in a column.
     ///
     /// Represented not with `f64`, but with `Decimal` type because the former doesn't support NaNs
-    /// and implements `Eq` trait, that we need for putting this struct into the HashSet.
+    /// and implements `Eq` trait, that we need for putting this struct into the 'HashSet'.
     pub(crate) frequency: Decimal,
 }
 
@@ -512,8 +512,8 @@ pub struct Histogram<T: Scalar> {
     /// **Note**: Values from mcv are not included in histogram buckets.
     ///
     /// Boundaries:
-    /// * i = 0 -> [b_0; b_1] (where `from` field of the bucket is included)
-    /// * i = 1 -> (b_1; b_2]
+    /// * i = 0 -> ['b_0'; 'b_1'] (where `from` field of the bucket is included)
+    /// * i = 1 -> ('b_1'; 'b_2']
     /// * ...
     /// * i = n -> (b_(n-2); b_(n-1)]
     pub(crate) buckets: HistogramBuckets<T>,
diff --git a/sbroad-core/src/errors.rs b/sbroad-core/src/errors.rs
index 88afdc47f..0353089e1 100644
--- a/sbroad-core/src/errors.rs
+++ b/sbroad-core/src/errors.rs
@@ -14,15 +14,15 @@ pub enum Entity {
     Acl,
     /// corresponding to enum Args
     Args,
-    /// corresponding to struct AbstractSyntaxTree
+    /// corresponding to struct 'AbstractSyntaxTree'
     AST,
-    /// corresponding to struct ParseNode
+    /// corresponding to struct 'ParseNode'
     ParseNode,
     /// corresponding to trait Aggregate
     Aggregate,
-    /// corresponding to struct AggregateSignature
+    /// corresponding to struct 'AggregateSignature'
     AggregateSignature,
-    /// corresponding to struct AggregateCollector
+    /// corresponding to struct 'AggregateCollector'
     AggregateCollector,
     /// corresponding to struct Buckets
     Buckets,
@@ -38,6 +38,8 @@ pub enum Entity {
     Column,
     /// CTE
     Cte,
+    /// corresponding to operations on DDL.
+    Ddl,
     /// corresponds to enum Distribution
     Distribution,
     /// tarantool distribution key
@@ -46,7 +48,7 @@ pub enum Entity {
     Engine,
     /// corresponds to enum Expression
     Expression,
-    /// corresponds to struct ExpressionMapper
+    /// corresponds to struct 'ExpressionMapper'
     ExpressionMapper,
     /// corresponds to struct Histogram
     Histogram,
@@ -164,6 +166,7 @@ impl fmt::Display for Entity {
             Entity::ClusterSchema => "cluster schema".to_smolstr(),
             Entity::Column => "column".to_smolstr(),
             Entity::Cte => "CTE".to_smolstr(),
+            Entity::Ddl => "DDL".to_smolstr(),
             Entity::Distribution => "distribution".to_smolstr(),
             Entity::DistributionKey => "distribution key".to_smolstr(),
             Entity::Engine => "engine".to_smolstr(),
diff --git a/sbroad-core/src/executor.rs b/sbroad-core/src/executor.rs
index b708f8af1..7b944a796 100644
--- a/sbroad-core/src/executor.rs
+++ b/sbroad-core/src/executor.rs
@@ -33,8 +33,8 @@ use crate::executor::engine::{helpers::materialize_values, Router, TableVersionM
 use crate::executor::ir::ExecutionPlan;
 use crate::executor::lru::Cache;
 use crate::frontend::Ast;
-use crate::ir::expression::NodeId;
-use crate::ir::operator::Relational;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{Motion, NodeId};
 use crate::ir::transformation::redistribution::MotionPolicy;
 use crate::ir::value::Value;
 use crate::ir::{Plan, Slices};
@@ -192,8 +192,7 @@ where
                     }
                 }
                 let motion = self.exec_plan.get_ir_plan().get_relation_node(*motion_id)?;
-
-                if let Relational::Motion { policy, .. } = motion {
+                if let Relational::Motion(Motion { policy, .. }) = motion {
                     match policy {
                         MotionPolicy::Segment(_) => {
                             // if child is values, then we can materialize it
@@ -225,6 +224,7 @@ where
                 }
 
                 let top_id = self.exec_plan.get_motion_subtree_root(*motion_id)?;
+
                 let buckets = self.bucket_discovery(top_id)?;
                 let virtual_table = self.coordinator.materialize_motion(
                     &mut self.exec_plan,
diff --git a/sbroad-core/src/executor/bucket.rs b/sbroad-core/src/executor/bucket.rs
index ebaae19bb..478761d66 100644
--- a/sbroad-core/src/executor/bucket.rs
+++ b/sbroad-core/src/executor/bucket.rs
@@ -7,13 +7,18 @@ use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::engine::{Router, Vshard};
 use crate::executor::Query;
 use crate::ir::distribution::Distribution;
-use crate::ir::expression::{Expression, NodeId};
 use crate::ir::helpers::RepeatableState;
-use crate::ir::operator::{Bool, JoinKind, Relational};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{
+    BoolExpr, Delete, Except, GroupBy, Having, Insert, Intersect, Join, Limit, Motion, Node,
+    NodeId, OrderBy, Projection, Row, ScanCte, ScanRelation, ScanSubQuery, Selection, Union,
+    UnionAll, Update, Values, ValuesRow,
+};
+use crate::ir::operator::{Bool, JoinKind};
 use crate::ir::transformation::redistribution::MotionPolicy;
 use crate::ir::tree::traversal::{LevelNode, PostOrderWithFilter, REL_CAPACITY};
 use crate::ir::value::Value;
-use crate::ir::Node;
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 
@@ -97,22 +102,22 @@ where
         let expr = ir_plan.get_expression_node(expr_id)?;
 
         // Try to collect buckets from expression of type `sharding_key = value`
-        if let Expression::Bool {
+        if let Expression::Bool(BoolExpr {
             op: Bool::Eq | Bool::In,
             left,
             right,
             ..
-        } = expr
+        }) = expr
         {
             let pairs = vec![(*left, *right), (*right, *left)];
             for (left_id, right_id) in pairs {
                 let left_expr = ir_plan.get_expression_node(left_id)?;
-                if !matches!(left_expr, Expression::Row { .. }) {
+                if !matches!(left_expr, Expression::Row(_)) {
                     continue;
                 }
 
                 let right_expr = ir_plan.get_expression_node(right_id)?;
-                let right_columns = if let Expression::Row { list, .. } = right_expr {
+                let right_columns = if let Expression::Row(Row { list, .. }) = right_expr {
                     list.clone()
                 } else {
                     continue;
@@ -160,8 +165,8 @@ where
                                     )
                                 })?;
                             let right_column_expr = ir_plan.get_expression_node(right_column_id)?;
-                            if let Expression::Constant { .. } = right_column_expr {
-                                values.push(right_column_expr.as_const_value_ref()?);
+                            if let Expression::Constant(_) = right_column_expr {
+                                values.push(ir_plan.as_const_value_ref(right_column_id)?);
                             } else {
                                 // One of the columns is not a constant. Skip this key.
                                 values = Vec::new();
@@ -277,10 +282,10 @@ where
         // if top's output has Distribution::Single then the whole subtree must executed only on
         // a single node, no need to traverse the subtree
         let top_output_id = self.exec_plan.get_ir_plan().get_relational_output(top_id)?;
-        if let Expression::Row {
+        if let Expression::Row(Row {
             distribution: Some(dist),
             ..
-        } = self
+        }) = self
             .exec_plan
             .get_ir_plan()
             .get_expression_node(top_output_id)?
@@ -292,7 +297,7 @@ where
 
         let ir_plan = self.exec_plan.get_ir_plan();
         let filter = |node_id: NodeId| -> bool {
-            if let Ok(Node::Relational(_)) = ir_plan.get_node(node_id) {
+            if let Ok(Node::Relational(..)) = ir_plan.get_node(node_id) {
                 return true;
             }
             false
@@ -312,9 +317,9 @@ where
 
             let rel = self.exec_plan.get_ir_plan().get_relation_node(node_id)?;
             match rel {
-                Relational::ScanRelation {
+                Relational::ScanRelation(ScanRelation {
                     output, relation, ..
-                } => {
+                }) => {
                     if ir_plan
                         .get_relation_or_error(relation.as_str())?
                         .is_global()
@@ -324,12 +329,12 @@ where
                         self.bucket_map.insert(*output, Buckets::All);
                     }
                 }
-                Relational::Motion {
+                Relational::Motion(Motion {
                     children,
                     policy,
                     output,
                     ..
-                } => match policy {
+                }) => match policy {
                     MotionPolicy::Full | MotionPolicy::Local => {
                         self.bucket_map.insert(*output, Buckets::Any);
                     }
@@ -391,9 +396,9 @@ where
                         ));
                     }
                 },
-                Relational::Delete { output, .. }
-                | Relational::Insert { output, .. }
-                | Relational::Update { output, .. } => {
+                Relational::Delete(Delete { output, .. })
+                | Relational::Insert(Insert { output, .. })
+                | Relational::Update(Update { output, .. }) => {
                     let child_id = ir_plan.get_relational_child(node_id, 0)?;
                     let child_buckets = self
                         .bucket_map
@@ -418,13 +423,13 @@ where
                     }
                     self.bucket_map.insert(*output, my_buckets);
                 }
-                Relational::Projection { output, .. }
-                | Relational::GroupBy { output, .. }
-                | Relational::Having { output, .. }
-                | Relational::OrderBy { output, .. }
-                | Relational::ScanCte { output, .. }
-                | Relational::ScanSubQuery { output, .. }
-                | Relational::Limit { output, .. } => {
+                Relational::Projection(Projection { output, .. })
+                | Relational::GroupBy(GroupBy { output, .. })
+                | Relational::Having(Having { output, .. })
+                | Relational::OrderBy(OrderBy { output, .. })
+                | Relational::ScanCte(ScanCte { output, .. })
+                | Relational::ScanSubQuery(ScanSubQuery { output, .. })
+                | Relational::Limit(Limit { output, .. }) => {
                     let child_id = ir_plan.get_relational_child(node_id, 0)?;
                     let child_rel = ir_plan.get_relation_node(child_id)?;
                     let child_buckets = self
@@ -440,7 +445,7 @@ where
                         .clone();
                     self.bucket_map.insert(*output, child_buckets);
                 }
-                Relational::Except { left, output, .. } => {
+                Relational::Except(Except { left, output, .. }) => {
                     // We are only interested in the first child (the left one).
                     // The rows from the second child would be transferred to the
                     // first child by the motion or already located in the first
@@ -454,18 +459,18 @@ where
                         .clone();
                     self.bucket_map.insert(*output, first_buckets);
                 }
-                Relational::Union {
+                Relational::Union(Union {
                     left,
                     right,
                     output,
                     ..
-                }
-                | Relational::UnionAll {
+                })
+                | Relational::UnionAll(UnionAll {
                     left,
                     right,
                     output,
                     ..
-                } => {
+                }) => {
                     let first_rel = self.exec_plan.get_ir_plan().get_relation_node(*left)?;
                     let second_rel = self.exec_plan.get_ir_plan().get_relation_node(*right)?;
                     let first_buckets = self
@@ -479,12 +484,12 @@ where
                     let buckets = first_buckets.disjunct(second_buckets)?;
                     self.bucket_map.insert(*output, buckets);
                 }
-                Relational::Intersect {
+                Relational::Intersect(Intersect {
                     left,
                     right,
                     output,
                     ..
-                } => {
+                }) => {
                     let first_rel = self.exec_plan.get_ir_plan().get_relation_node(*left)?;
                     let second_rel = self.exec_plan.get_ir_plan().get_relation_node(*right)?;
                     let first_buckets = self
@@ -498,12 +503,12 @@ where
                     let buckets = first_buckets.disjunct(second_buckets)?;
                     self.bucket_map.insert(*output, buckets);
                 }
-                Relational::Selection {
+                Relational::Selection(Selection {
                     children,
                     filter,
                     output,
                     ..
-                } => {
+                }) => {
                     // We need to get the buckets of the child node for the case
                     // when the filter returns no buckets to reduce.
                     let child_id = children.first().ok_or_else(|| {
@@ -529,12 +534,12 @@ where
                     self.bucket_map
                         .insert(output_id, child_buckets.conjuct(&filter_buckets)?);
                 }
-                Relational::Join {
+                Relational::Join(Join {
                     children,
                     condition,
                     output,
                     kind,
-                } => {
+                }) => {
                     if let (Some(inner_id), Some(outer_id)) = (children.first(), children.get(1)) {
                         let inner_rel =
                             self.exec_plan.get_ir_plan().get_relation_node(*inner_id)?;
@@ -581,7 +586,8 @@ where
                         ));
                     }
                 }
-                Relational::Values { output, .. } | Relational::ValuesRow { output, .. } => {
+                Relational::Values(Values { output, .. })
+                | Relational::ValuesRow(ValuesRow { output, .. }) => {
                     // At the moment values rows are located on the coordinator,
                     // so there are no buckets to execute on.
                     self.bucket_map.insert(*output, Buckets::new_empty());
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index 509a93a58..d65d8407b 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -7,7 +7,7 @@ use tarantool::tuple::Tuple;
 
 use crate::cbo::histogram::Scalar;
 use crate::cbo::{ColumnStats, TableColumnPair, TableStats};
-use crate::ir::expression::NodeId;
+use crate::ir::node::NodeId;
 use crate::utils::MutexLike;
 use std::any::Any;
 
diff --git a/sbroad-core/src/executor/engine/helpers.rs b/sbroad-core/src/executor/engine/helpers.rs
index 77a7cc8a9..1d1e2a0b4 100644
--- a/sbroad-core/src/executor/engine/helpers.rs
+++ b/sbroad-core/src/executor/engine/helpers.rs
@@ -1,6 +1,15 @@
 use ahash::AHashMap;
 
-use crate::{error, ir::expression::NodeId, utils::MutexLike};
+use crate::{
+    error,
+    ir::node::{
+        expression::{ExprOwned, Expression},
+        relational::{RelOwned, Relational},
+        Alias, Constant, Delete, Insert, Limit, Motion, NodeId, NodeOwned, Update, Values,
+        ValuesRow,
+    },
+    utils::MutexLike,
+};
 use itertools::enumerate;
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 use std::{
@@ -42,13 +51,11 @@ use crate::{
     },
     ir::{
         distribution::Distribution,
-        expression::Expression,
-        operator::Relational,
         relation::{Column, ColumnRole, Type},
         transformation::redistribution::{MotionKey, MotionPolicy},
         tree::Snapshot,
         value::Value,
-        Node, Plan,
+        Plan,
     },
 };
 use sbroad_proc::otm_child_span;
@@ -140,10 +147,10 @@ pub fn build_required_binary(exec_plan: &mut ExecutionPlan) -> Result<Binary, Sb
                 let child_id = ir.get_relational_child(top_id, 0)?;
                 let is_cacheable = matches!(
                     ir.get_relation_node(child_id)?,
-                    Relational::Motion {
+                    Relational::Motion(Motion {
                         policy: MotionPolicy::Local | MotionPolicy::LocalSegment { .. },
                         ..
-                    }
+                    })
                 );
                 if is_cacheable {
                     let cacheable_subtree_root_id = exec_plan.get_motion_subtree_root(child_id)?;
@@ -187,16 +194,18 @@ pub fn build_optional_binary(mut exec_plan: ExecutionPlan) -> Result<Binary, Sbr
             let sp_top_id = plan.get_top()?;
             let sp_top = plan.get_relation_node(sp_top_id)?;
             let motion_id = match sp_top {
-                Relational::Insert { children, .. }
-                | Relational::Delete { children, .. }
-                | Relational::Update { children, .. } => *children.first().ok_or_else(|| {
-                    SbroadError::Invalid(
-                        Entity::Plan,
-                        Some(format_smolstr!(
-                            "expected at least one child under DML node {sp_top:?}",
-                        )),
-                    )
-                })?,
+                Relational::Insert(Insert { children, .. })
+                | Relational::Update(Update { children, .. })
+                | Relational::Delete(Delete { children, .. }) => {
+                    *children.first().ok_or_else(|| {
+                        SbroadError::Invalid(
+                            Entity::Plan,
+                            Some(format_smolstr!(
+                                "expected at least one child under DML node {sp_top:?}",
+                            )),
+                        )
+                    })?
+                }
                 _ => {
                     return Err(SbroadError::Invalid(
                         Entity::Plan,
@@ -205,7 +214,7 @@ pub fn build_optional_binary(mut exec_plan: ExecutionPlan) -> Result<Binary, Sbr
                 }
             };
             let motion = plan.get_relation_node(motion_id)?;
-            let Relational::Motion { policy, .. } = motion else {
+            let Relational::Motion(Motion { policy, .. }) = motion else {
                 return Err(SbroadError::Invalid(
                     Entity::Plan,
                     Some(format_smolstr!(
@@ -437,12 +446,12 @@ pub fn init_local_update_tuple_builder(
     vtable: &VirtualTable,
     update_id: NodeId,
 ) -> Result<TupleBuilderPattern, SbroadError> {
-    if let Relational::Update {
+    if let Relational::Update(Update {
         relation,
         update_columns_map,
         pk_positions,
         ..
-    } = plan.get_relation_node(update_id)?
+    }) = plan.get_relation_node(update_id)?
     {
         let mut commands: TupleBuilderPattern =
             Vec::with_capacity(update_columns_map.len() + pk_positions.len());
@@ -664,9 +673,9 @@ fn init_sharded_update_tuple_builder(
     vtable: &VirtualTable,
     update_id: NodeId,
 ) -> Result<TupleBuilderPattern, SbroadError> {
-    let Relational::Update {
+    let Relational::Update(Update {
         update_columns_map, ..
-    } = plan.get_relation_node(update_id)?
+    }) = plan.get_relation_node(update_id)?
     else {
         return Err(SbroadError::Invalid(
             Entity::Node,
@@ -759,7 +768,7 @@ pub fn empty_query_result(
             for col_id in columns {
                 let column = ir_plan.get_expression_node(*col_id)?;
                 let column_type = column.calculate_type(ir_plan)?;
-                let column_name = if let Expression::Alias { name, .. } = column {
+                let column_name = if let Expression::Alias(Alias { name, .. }) = column {
                     name.clone()
                 } else {
                     return Err(SbroadError::Invalid(
@@ -811,7 +820,7 @@ pub fn explain_format(explain: &str) -> Result<Box<dyn Any>, SbroadError> {
 fn has_zero_limit_clause(plan: &ExecutionPlan) -> Result<bool, SbroadError> {
     let ir = plan.get_ir_plan();
     let top_id = ir.get_top()?;
-    if let Relational::Limit { limit, .. } = ir.get_relation_node(top_id)? {
+    if let Relational::Limit(Limit { limit, .. }) = ir.get_relation_node(top_id)? {
         return Ok(*limit == 0);
     }
     Ok(false)
@@ -900,10 +909,10 @@ pub(crate) fn materialize_values(
 ) -> Result<Option<VirtualTable>, SbroadError> {
     // Check that the motion node has a local segment policy.
     let motion_node = plan.get_ir_plan().get_relation_node(motion_node_id)?;
-    if let Relational::Motion {
+    if let Relational::Motion(Motion {
         policy: MotionPolicy::LocalSegment(_),
         ..
-    } = motion_node
+    }) = motion_node
     {
     } else {
         return Ok(None);
@@ -917,17 +926,16 @@ pub(crate) fn materialize_values(
     let child_id = plan.get_motion_child(motion_node_id)?;
     if !matches!(
         plan.get_ir_plan().get_relation_node(child_id)?,
-        Relational::Values { .. }
+        Relational::Values(_)
     ) {
         return Ok(None);
     }
-    let child_node_ref = plan.get_mut_ir_plan().get_mut_node(child_id)?;
-    let child_node = std::mem::replace(child_node_ref, Node::Parameter(None));
-    if let Node::Relational(Relational::Values {
+    let child_node = plan.get_mut_ir_plan().replace_with_stub(child_id);
+    if let NodeOwned::Relational(RelOwned::Values(Values {
         ref children,
         output,
         ..
-    }) = child_node
+    })) = child_node
     {
         // Build a new virtual table (check that all the rows are made of constants only).
         let mut vtable = VirtualTable::new();
@@ -935,7 +943,7 @@ pub(crate) fn materialize_values(
 
         let columns_len = if let Some(first_row_id) = children.first() {
             let row_node = plan.get_ir_plan().get_relation_node(*first_row_id)?;
-            let Relational::ValuesRow { data, .. } = row_node else {
+            let Relational::ValuesRow(ValuesRow { data, .. }) = row_node else {
                 return Err(SbroadError::Invalid(
                     Entity::Node,
                     Some(format_smolstr!("Expected ValuesRow, got {row_node:?}")),
@@ -956,7 +964,7 @@ pub(crate) fn materialize_values(
         let mut nullable_column_indices = HashSet::with_capacity(columns_len);
         for row_id in children {
             let row_node = plan.get_ir_plan().get_relation_node(*row_id)?;
-            if let Relational::ValuesRow { data, children, .. } = row_node {
+            if let Relational::ValuesRow(ValuesRow { data, children, .. }) = row_node {
                 // Check that there are no subqueries in the values node.
                 // (If any we'll need to materialize them first with dispatch
                 // to the storages.)
@@ -982,9 +990,10 @@ pub(crate) fn materialize_values(
                                     format_smolstr!("at position {idx} in the row"),
                                 )
                             })?;
-                    let column_node_ref = plan.get_mut_ir_plan().get_mut_node(column_id)?;
-                    let column_node = std::mem::replace(column_node_ref, Node::Parameter(None));
-                    if let Node::Expression(Expression::Constant { value, .. }) = column_node {
+                    let column_node = plan.get_mut_ir_plan().replace_with_stub(column_id);
+                    if let NodeOwned::Expression(ExprOwned::Constant(Constant { value, .. })) =
+                        column_node
+                    {
                         if let Value::Null = value {
                             nullable_column_indices.insert(idx);
                         }
@@ -1009,16 +1018,13 @@ pub(crate) fn materialize_values(
             }
         }
         // Build virtual table's columns.
-        let output_cols = plan
-            .get_ir_plan()
-            .get_expression_node(output)?
-            .get_row_list()?;
+        let output_cols = plan.get_ir_plan().get_row_list(output)?;
         let columns = vtable.get_mut_columns();
         columns.reserve(output_cols.len());
         for (idx, column_id) in enumerate(output_cols) {
             let is_nullable = nullable_column_indices.contains(&idx);
             let alias = plan.get_ir_plan().get_expression_node(*column_id)?;
-            if let Expression::Alias { name, .. } = alias {
+            if let Expression::Alias(Alias { name, .. }) = alias {
                 let column = Column {
                     name: name.clone(),
                     r#type: Type::Scalar,
@@ -1530,9 +1536,9 @@ where
     let top_id = plan.get_top()?;
     let top = plan.get_relation_node(top_id)?;
     match top {
-        Relational::Insert { .. } => execute_insert_on_storage(runtime, &mut optional, required),
-        Relational::Delete { .. } => execute_delete_on_storage(runtime, &mut optional, required),
-        Relational::Update { .. } => execute_update_on_storage(runtime, &mut optional, required),
+        Relational::Insert(_) => execute_insert_on_storage(runtime, &mut optional, required),
+        Relational::Delete(_) => execute_delete_on_storage(runtime, &mut optional, required),
+        Relational::Update(_) => execute_update_on_storage(runtime, &mut optional, required),
         _ => Err(SbroadError::Invalid(
             Entity::Plan,
             Some(format_smolstr!(
@@ -2216,7 +2222,7 @@ pub fn try_get_metadata_from_plan(
     for col_id in columns {
         let column = ir.get_expression_node(*col_id)?;
         let column_type = column.calculate_type(ir)?.to_string();
-        let column_name = if let Expression::Alias { name, .. } = column {
+        let column_name = if let Expression::Alias(Alias { name, .. }) = column {
             name.to_string()
         } else {
             return Err(SbroadError::Invalid(
diff --git a/sbroad-core/src/executor/engine/helpers/storage.rs b/sbroad-core/src/executor/engine/helpers/storage.rs
index c1756ffb9..14d741029 100644
--- a/sbroad-core/src/executor/engine/helpers/storage.rs
+++ b/sbroad-core/src/executor/engine/helpers/storage.rs
@@ -16,8 +16,8 @@ use crate::errors::SbroadError;
 use crate::executor::engine::helpers::proxy::sql_cache_proxy;
 use crate::executor::engine::helpers::table_name;
 use crate::executor::lru::DEFAULT_CAPACITY;
+use crate::ir::node::NodeId;
 use crate::ir::value::{EncodedValue, Value};
-use crate::ir::expression::NodeId;
 use crate::otm::child_span;
 use crate::utils::ByteCounter;
 
diff --git a/sbroad-core/src/executor/engine/helpers/vshard.rs b/sbroad-core/src/executor/engine/helpers/vshard.rs
index 87302d9d5..d9d08730e 100644
--- a/sbroad-core/src/executor/engine/helpers/vshard.rs
+++ b/sbroad-core/src/executor/engine/helpers/vshard.rs
@@ -18,19 +18,21 @@ use crate::{
         protocol::{FullMessage, RequiredMessage},
         result::ProducerResult,
     },
-    ir::{
-        expression::NodeId,
+    ir::node::{
+        relational::{MutRelational, Relational},
+        Motion, Node, NodeId,
+    },
+    otm::child_span,
+};
+use crate::ir::{
         helpers::RepeatableState,
-        operator::Relational,
         transformation::redistribution::{MotionOpcode, MotionPolicy},
         tree::{
             relation::RelationalIterator,
             traversal::{LevelNode, PostOrderWithFilter, REL_CAPACITY},
         },
-        Node, Plan,
-    },
-    otm::child_span,
-};
+        Plan,
+    };
 use ahash::AHashMap;
 use rand::{thread_rng, Rng};
 use sbroad_proc::otm_child_span;
@@ -750,7 +752,9 @@ impl Plan {
     // return true if given node is Motion containing seriliaze as empty
     // opcode. If `check_enabled` is true checks that the opcode is enabled.
     fn is_serialize_as_empty_motion(&self, node_id: NodeId, check_enabled: bool) -> bool {
-        if let Ok(Node::Relational(Relational::Motion { program, .. })) = self.get_node(node_id) {
+        if let Ok(Node::Relational(Relational::Motion(Motion { program, .. }))) =
+            self.get_node(node_id)
+        {
             if let Some(op) = program
                 .0
                 .iter()
@@ -787,7 +791,7 @@ impl Plan {
         let filter = |node_id: NodeId| -> bool {
             matches!(
                 self.get_node(node_id),
-                Ok(Node::Relational(Relational::Motion { .. }))
+                Ok(Node::Relational(Relational::Motion(_)))
             )
         };
         let mut dfs =
@@ -809,7 +813,7 @@ impl Plan {
             let is_motion = |node_id: NodeId| -> bool {
                 matches!(
                     self.get_node(node_id),
-                    Ok(Node::Relational(Relational::Motion { .. }))
+                    Ok(Node::Relational(Relational::Motion(_)))
                 )
             };
             let mut all_motions = Vec::new();
@@ -913,9 +917,9 @@ fn disable_serialize_as_empty_opcode(
     info: &SerializeAsEmptyInfo,
 ) -> Result<(), SbroadError> {
     for motion_id in &info.target_motion_ids {
-        let program = if let Relational::Motion {
+        let program = if let MutRelational::Motion(Motion {
             policy, program, ..
-        } = sub_plan
+        }) = sub_plan
             .get_mut_ir_plan()
             .get_mut_relation_node(*motion_id)?
         {
diff --git a/sbroad-core/src/executor/engine/mock.rs b/sbroad-core/src/executor/engine/mock.rs
index 900d90740..6fcf8cfba 100644
--- a/sbroad-core/src/executor/engine/mock.rs
+++ b/sbroad-core/src/executor/engine/mock.rs
@@ -30,8 +30,8 @@ use crate::executor::result::ProducerResult;
 use crate::executor::vtable::VirtualTable;
 use crate::executor::Cache;
 use crate::frontend::sql::ast::AbstractSyntaxTree;
-use crate::ir::expression::NodeId;
 use crate::ir::function::Function;
+use crate::ir::node::NodeId;
 use crate::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type};
 use crate::ir::tree::Snapshot;
 use crate::ir::value::{LuaValue, Value};
diff --git a/sbroad-core/src/executor/ir.rs b/sbroad-core/src/executor/ir.rs
index 5a4fb3743..d8f23c5e4 100644
--- a/sbroad-core/src/executor/ir.rs
+++ b/sbroad-core/src/executor/ir.rs
@@ -8,12 +8,19 @@ use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 use crate::errors::{Action, Entity, SbroadError};
 use crate::executor::engine::Vshard;
 use crate::executor::vtable::{VirtualTable, VirtualTableMap};
-use crate::ir::expression::{Expression, NodeId};
-use crate::ir::operator::{OrderByElement, OrderByEntity, Relational};
+use crate::ir::node::expression::ExprOwned;
+use crate::ir::node::relational::{MutRelational, RelOwned, Relational};
+use crate::ir::node::{
+    Alias, ArenaType, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Delete, ExprInParentheses,
+    GroupBy, Having, Insert, Join, Motion, Node, Node136, NodeId, NodeOwned, OrderBy, Reference,
+    Row, ScanCte, ScanRelation, ScanSubQuery, Selection, StableFunction, Trim, UnaryExpr, Update,
+    ValuesRow,
+};
+use crate::ir::operator::{OrderByElement, OrderByEntity};
 use crate::ir::relation::SpaceEngine;
 use crate::ir::transformation::redistribution::{MotionOpcode, MotionPolicy};
 use crate::ir::tree::traversal::{LevelNode, PostOrder};
-use crate::ir::{ArenaType, ExecuteOptions, Node, Plan};
+use crate::ir::{ExecuteOptions, Plan};
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 
@@ -156,7 +163,7 @@ impl ExecutionPlan {
         runtime: &impl Vshard,
     ) -> Result<(), SbroadError> {
         let mut vtable = vtable;
-        let program_len = if let Relational::Motion { program, .. } =
+        let program_len = if let Relational::Motion(Motion { program, .. }) =
             self.get_ir_plan().get_relation_node(*motion_id)?
         {
             program.0.len()
@@ -253,8 +260,8 @@ impl ExecutionPlan {
     /// nodes that are not referenced by actual plan tree.
     #[must_use]
     pub fn has_customization_opcodes(&self) -> bool {
-        for node in &self.get_ir_plan().nodes {
-            if let Node::Relational(Relational::Motion { program, .. }) = node {
+        for node in self.get_ir_plan().nodes.iter136() {
+            if let Node136::Motion(Motion { program, .. }) = node {
                 if program
                     .0
                     .iter()
@@ -273,7 +280,7 @@ impl ExecutionPlan {
     /// - node is not `Relation` type
     /// - node is not `Motion` type
     pub fn get_motion_policy(&self, node_id: NodeId) -> Result<MotionPolicy, SbroadError> {
-        if let Relational::Motion { policy, .. } = &self.plan.get_relation_node(node_id)? {
+        if let Relational::Motion(Motion { policy, .. }) = &self.plan.get_relation_node(node_id)? {
             return Ok(policy.clone());
         }
 
@@ -291,8 +298,8 @@ impl ExecutionPlan {
         let child_id = &self.get_motion_child(node_id)?;
         let child_rel = self.get_ir_plan().get_relation_node(*child_id)?;
         match child_rel {
-            Relational::ScanSubQuery { alias, .. } => Ok(alias.clone()),
-            Relational::ScanCte { alias, .. } => Ok(Some(alias.clone())),
+            Relational::ScanSubQuery(ScanSubQuery { alias, .. }) => Ok(alias.clone()),
+            Relational::ScanCte(ScanCte { alias, .. }) => Ok(Some(alias.clone())),
             _ => Ok(None),
         }
     }
@@ -368,9 +375,9 @@ impl ExecutionPlan {
     /// - not a motion node
     pub fn unlink_motion_subtree(&mut self, motion_id: NodeId) -> Result<(), SbroadError> {
         let motion = self.get_mut_ir_plan().get_mut_relation_node(motion_id)?;
-        if let Relational::Motion {
+        if let MutRelational::Motion(Motion {
             ref mut children, ..
-        } = motion
+        }) = motion
         {
             *children = vec![];
         } else {
@@ -408,7 +415,7 @@ impl ExecutionPlan {
             .filter(|id| {
                 matches!(
                     plan.get_node(*id),
-                    Ok(Node::Relational(Relational::ScanCte { .. }))
+                    Ok(Node::Relational(Relational::ScanCte(_)))
                 )
             })
             .collect::<Vec<_>>();
@@ -420,15 +427,13 @@ impl ExecutionPlan {
         let mut cte_amount = 0;
         for cte_id in &cte_scans {
             let cte_node = plan.get_relation_node(*cte_id)?;
-            let Relational::ScanCte { child, .. } = cte_node else {
+            let Relational::ScanCte(ScanCte { child, .. }) = cte_node else {
                 unreachable!("Expected CTE scan node.");
             };
             let child_id = *child;
-            let offset = usize::try_from(child_id.offset).unwrap();
-            if all_cte_nodes_capacity < offset {
-                all_cte_nodes_capacity = offset;
+            if all_cte_nodes_capacity < child_id.offset as usize {
+                all_cte_nodes_capacity = child_id.offset as usize;
             }
-
             cte_amount += 1;
         }
         all_cte_nodes_capacity += 1;
@@ -461,7 +466,6 @@ impl ExecutionPlan {
             HashMap::with_capacity(vtables_capacity);
 
         let mut new_plan = Plan::new();
-        new_plan.nodes.reserve(nodes.len());
         for LevelNode(_, node_id) in nodes {
             // We have already processed this node (sub-queries in BETWEEN
             // and CTEs can be referred twice).
@@ -469,33 +473,33 @@ impl ExecutionPlan {
                 continue;
             }
 
-            // Node from original plan that we'll take and replace with mock parameter node.
-            let dst_node = self.get_mut_ir_plan().get_mut_node(node_id)?;
-            let next_id = new_plan.nodes.next_id(ArenaType::Default);
+            let mut_plan = self.get_mut_ir_plan();
 
             // Replace the node with some invalid value.
-            // TODO: introduce some new enum variant for this purpose.
-            let mut node: Node = if cte_ids.contains(&node_id) {
-                dst_node.clone()
+            let mut node: NodeOwned = if cte_ids.contains(&node_id) {
+                mut_plan.get_mut_node(node_id)?.get_common_node()
             } else {
-                std::mem::replace(dst_node, Node::Parameter(None))
+                mut_plan.replace_with_stub(node_id)
             };
+
+            let mut relational_output_id: Option<NodeId> = None;
             let ir_plan = self.get_ir_plan();
             match node {
-                Node::Relational(ref mut rel) => {
+                NodeOwned::Relational(ref mut rel) => {
                     match rel {
-                        Relational::Selection {
+                        RelOwned::Selection(Selection {
                             filter: ref mut expr_id,
                             ..
-                        }
-                        | Relational::Having {
+                        })
+                        | RelOwned::Having(Having {
                             filter: ref mut expr_id,
                             ..
-                        }
-                        | Relational::Join {
+                        })
+                        | RelOwned::Join(Join {
                             condition: ref mut expr_id,
                             ..
-                        } => {
+                        }) => {
+                            let next_id = new_plan.nodes.next_id(ArenaType::Arena64);
                             // We transform selection's, having's filter and join's condition to DNF for a better bucket calculation.
                             // But as a result we can produce an extremely verbose SQL query from such a plan (tarantool's
                             // parser can fail to parse such SQL).
@@ -506,10 +510,10 @@ impl ExecutionPlan {
                             *expr_id = subtree_map.get_id(*undo_expr_id);
                             new_plan.replace_parent_in_subtree(*expr_id, None, Some(next_id))?;
                         }
-                        Relational::ScanRelation { relation, .. }
-                        | Relational::Insert { relation, .. }
-                        | Relational::Delete { relation, .. }
-                        | Relational::Update { relation, .. } => {
+                        RelOwned::ScanRelation(ScanRelation { relation, .. })
+                        | RelOwned::Insert(Insert { relation, .. })
+                        | RelOwned::Delete(Delete { relation, .. })
+                        | RelOwned::Update(Update { relation, .. }) => {
                             let table = ir_plan
                                 .relations
                                 .get(relation)
@@ -521,12 +525,13 @@ impl ExecutionPlan {
                                 .clone();
                             new_plan.add_rel(table);
                         }
-                        Relational::Motion {
+                        RelOwned::Motion(Motion {
                             children, policy, ..
-                        } => {
+                        }) => {
                             if let Some(vtable) =
                                 self.get_vtables().map_or_else(|| None, |v| v.get(&node_id))
                             {
+                                let next_id = new_plan.nodes.next_id(ArenaType::Arena136);
                                 new_vtables.insert(next_id, Rc::clone(vtable));
                             }
                             // We should not remove the child of a local motion node.
@@ -535,7 +540,8 @@ impl ExecutionPlan {
                                 *children = Vec::new();
                             }
                         }
-                        Relational::GroupBy { gr_cols, .. } => {
+                        RelOwned::GroupBy(GroupBy { gr_cols, .. }) => {
+                            let next_id = new_plan.nodes.next_id(ArenaType::Arena64);
                             let mut new_cols: Vec<NodeId> = Vec::with_capacity(gr_cols.len());
                             for col_id in gr_cols.iter() {
                                 let new_col_id = subtree_map.get_id(*col_id);
@@ -548,9 +554,10 @@ impl ExecutionPlan {
                             }
                             *gr_cols = new_cols;
                         }
-                        Relational::OrderBy {
+                        RelOwned::OrderBy(OrderBy {
                             order_by_elements, ..
-                        } => {
+                        }) => {
+                            let next_id = new_plan.nodes.next_id(ArenaType::Arena64);
                             let mut new_elements: Vec<OrderByElement> =
                                 Vec::with_capacity(order_by_elements.len());
                             for element in order_by_elements.iter() {
@@ -577,84 +584,84 @@ impl ExecutionPlan {
                             }
                             *order_by_elements = new_elements;
                         }
-                        Relational::ValuesRow { data, .. } => {
+                        RelOwned::ValuesRow(ValuesRow { data, .. }) => {
                             *data = subtree_map.get_id(*data);
                         }
-                        Relational::Except { .. }
-                        | Relational::Intersect { .. }
-                        | Relational::Projection { .. }
-                        | Relational::ScanSubQuery { .. }
-                        | Relational::ScanCte { .. }
-                        | Relational::Union { .. }
-                        | Relational::UnionAll { .. }
-                        | Relational::Values { .. }
-                        | Relational::Limit { .. } => {}
+                        RelOwned::Except { .. }
+                        | RelOwned::Intersect { .. }
+                        | RelOwned::Projection { .. }
+                        | RelOwned::ScanSubQuery { .. }
+                        | RelOwned::ScanCte { .. }
+                        | RelOwned::Union { .. }
+                        | RelOwned::UnionAll { .. }
+                        | RelOwned::Values { .. }
+                        | RelOwned::Limit { .. } => {}
                     }
 
                     for child_id in rel.mut_children() {
                         *child_id = subtree_map.get_id(*child_id);
                     }
 
-                    let output = rel.output();
-                    *rel.mut_output() = subtree_map.get_id(output);
-                    new_plan.replace_parent_in_subtree(rel.output(), None, Some(next_id))?;
+                    let output = rel.mut_output();
+                    *rel.mut_output() = subtree_map.get_id(*output);
+                    relational_output_id = Some(*rel.mut_output());
                 }
-                Node::Expression(ref mut expr) => match expr {
-                    Expression::Alias { ref mut child, .. }
-                    | Expression::ExprInParentheses { ref mut child }
-                    | Expression::Cast { ref mut child, .. }
-                    | Expression::Unary { ref mut child, .. } => {
+                NodeOwned::Expression(ref mut expr) => match expr {
+                    ExprOwned::Alias(Alias { ref mut child, .. })
+                    | ExprOwned::ExprInParentheses(ExprInParentheses { ref mut child })
+                    | ExprOwned::Cast(Cast { ref mut child, .. })
+                    | ExprOwned::Unary(UnaryExpr { ref mut child, .. }) => {
                         *child = subtree_map.get_id(*child);
                     }
-                    Expression::Bool {
+                    ExprOwned::Bool(BoolExpr {
                         ref mut left,
                         ref mut right,
                         ..
-                    }
-                    | Expression::Arithmetic {
+                    })
+                    | ExprOwned::Arithmetic(ArithmeticExpr {
                         ref mut left,
                         ref mut right,
                         ..
-                    }
-                    | Expression::Concat {
+                    })
+                    | ExprOwned::Concat(Concat {
                         ref mut left,
                         ref mut right,
                         ..
-                    } => {
+                    }) => {
                         *left = subtree_map.get_id(*left);
                         *right = subtree_map.get_id(*right);
                     }
-                    Expression::Trim {
+                    ExprOwned::Trim(Trim {
                         ref mut pattern,
                         ref mut target,
                         ..
-                    } => {
+                    }) => {
                         if let Some(pattern) = pattern {
                             *pattern = subtree_map.get_id(*pattern);
                         }
                         *target = subtree_map.get_id(*target);
                     }
-                    Expression::Reference { ref mut parent, .. } => {
+                    ExprOwned::Reference(Reference { ref mut parent, .. }) => {
                         // The new parent node id MUST be set while processing the relational nodes.
                         *parent = None;
                     }
-                    Expression::Row {
+                    ExprOwned::Row(Row {
                         list: ref mut children,
                         ..
-                    }
-                    | Expression::StableFunction {
+                    })
+                    | ExprOwned::StableFunction(StableFunction {
                         ref mut children, ..
-                    } => {
+                    }) => {
                         for child in children {
                             *child = subtree_map.get_id(*child);
                         }
                     }
-                    Expression::Constant { .. } | Expression::CountAsterisk => {}
-                    Expression::Case {
+                    ExprOwned::Constant { .. } | ExprOwned::CountAsterisk { .. } => {}
+                    ExprOwned::Case(Case {
                         search_expr,
                         when_blocks,
                         else_expr,
-                    } => {
+                    }) => {
                         if let Some(search_expr) = search_expr {
                             *search_expr = subtree_map.get_id(*search_expr);
                         }
@@ -667,15 +674,23 @@ impl ExecutionPlan {
                         }
                     }
                 },
-                Node::Parameter { .. } => {}
-                Node::Ddl { .. } | Node::Acl { .. } | Node::Block { .. } => {
+                NodeOwned::Parameter { .. } => {}
+                NodeOwned::Invalid { .. }
+                | NodeOwned::Ddl { .. }
+                | NodeOwned::Acl { .. }
+                | NodeOwned::Block { .. } => {
                     panic!("Unexpected node in `take_subtree`: {node:?}")
                 }
             }
-            new_plan.nodes.push(node);
-            subtree_map.insert(node_id, next_id);
+
+            let id = new_plan.nodes.push(node.into());
+            if let Some(output_id) = relational_output_id {
+                new_plan.replace_parent_in_subtree(output_id, None, Some(id))?;
+            }
+
+            subtree_map.insert(node_id, id);
             if top_id == node_id {
-                new_plan.set_top(next_id)?;
+                new_plan.set_top(id)?;
             }
         }
 
@@ -694,7 +709,6 @@ impl ExecutionPlan {
         };
         Ok(new_exec_plan)
     }
-
     /// # Errors
     /// - execution plan is invalid
     pub fn query_type(&self) -> Result<QueryType, SbroadError> {
diff --git a/sbroad-core/src/executor/protocol.rs b/sbroad-core/src/executor/protocol.rs
index 3905635d1..2e41f1c5a 100644
--- a/sbroad-core/src/executor/protocol.rs
+++ b/sbroad-core/src/executor/protocol.rs
@@ -15,7 +15,7 @@ use crate::ir::Options;
 use crate::otm::{current_id, extract_context, inject_context};
 
 use crate::executor::engine::TableVersionMap;
-use crate::ir::expression::NodeId;
+use crate::ir::node::NodeId;
 #[cfg(not(feature = "mock"))]
 use opentelemetry::trace::TraceContextExt;
 
diff --git a/sbroad-core/src/executor/result.rs b/sbroad-core/src/executor/result.rs
index 49a8bffa2..d2cfa4a74 100644
--- a/sbroad-core/src/executor/result.rs
+++ b/sbroad-core/src/executor/result.rs
@@ -20,12 +20,12 @@ use tarantool::tuple::Encode;
 use crate::debug;
 use crate::errors::SbroadError;
 use crate::executor::vtable::{VTableTuple, VirtualTable};
-use crate::ir::expression::NodeId;
-use crate::ir::operator::Relational;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{Node, NodeId};
 use crate::ir::relation::{Column, ColumnRole, Type};
 use crate::ir::tree::traversal::{PostOrderWithFilter, REL_CAPACITY};
 use crate::ir::value::{LuaValue, Value};
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 
 pub type ExecutorTuple = Vec<LuaValue>;
 
@@ -260,7 +260,7 @@ impl Plan {
     /// - If relational iterator fails to return a correct node.
     pub fn subtree_contains_values(&self, top_id: NodeId) -> Result<bool, SbroadError> {
         let filter = |node_id: NodeId| -> bool {
-            if let Ok(Node::Relational(Relational::Values { .. })) = self.get_node(node_id) {
+            if let Ok(Node::Relational(Relational::Values(_))) = self.get_node(node_id) {
                 return true;
             }
             false
diff --git a/sbroad-core/src/executor/tests.rs b/sbroad-core/src/executor/tests.rs
index 71f536340..c0a27f82c 100644
--- a/sbroad-core/src/executor/tests.rs
+++ b/sbroad-core/src/executor/tests.rs
@@ -1,11 +1,10 @@
 use pretty_assertions::assert_eq;
 
-use crate::{backend::sql::ir::PatternWithParams, ir::expression::NodeId};
+use crate::backend::sql::ir::PatternWithParams;
 
 use crate::executor::engine::mock::RouterRuntimeMock;
 use crate::executor::result::ProducerResult;
 use crate::executor::vtable::VirtualTable;
-use crate::ir::operator::Relational;
 use crate::ir::tests::column_integer_user_non_null;
 use crate::ir::transformation::redistribution::MotionPolicy;
 use smol_str::SmolStr;
@@ -178,7 +177,7 @@ fn linker_test() {
                         "{} {} {}",
                         r#"SELECT "test_space"."FIRST_NAME""#,
                         r#"FROM "test_space""#,
-                        r#"WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_76")"#,
+                        r#"WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_0136")"#,
                         ), vec![],
                     )
                 )
@@ -193,7 +192,7 @@ fn linker_test() {
                         "{} {} {}",
                         r#"SELECT "test_space"."FIRST_NAME""#,
                         r#"FROM "test_space""#,
-                        r#"WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_76")"#,
+                        r#"WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_0136")"#,
                         ), vec![],
                     )
                 )
@@ -268,7 +267,7 @@ fn union_linker_test() {
                     r#"FROM "test_space_hist""#,
                     r#"WHERE ("test_space_hist"."sys_op") > (?)"#,
                     r#") as "t1""#,
-                    r#"WHERE ("t1"."id") in (SELECT "identification_number" FROM "TMP_test_219")"#,
+                    r#"WHERE ("t1"."id") in (SELECT "identification_number" FROM "TMP_test_0136")"#,
                 ),
                 vec![Value::from(0_u64), Value::from(0_u64)],
             ))),
@@ -288,7 +287,7 @@ fn union_linker_test() {
                     r#"FROM "test_space_hist""#,
                     r#"WHERE ("test_space_hist"."sys_op") > (?)"#,
                     r#") as "t1""#,
-                    r#"WHERE ("t1"."id") in (SELECT "identification_number" FROM "TMP_test_219")"#,
+                    r#"WHERE ("t1"."id") in (SELECT "identification_number" FROM "TMP_test_0136")"#,
                 ),
                 vec![Value::from(0_u64), Value::from(0_u64)],
             ))),
@@ -370,7 +369,7 @@ WHERE "t3"."id" = 2 AND "t8"."identification_number" = 2"#;
                 r#"WHERE ("test_space_hist"."sysFrom") <= (?)"#,
                 r#") as "t3""#,
                 r#"INNER JOIN"#,
-                r#"(SELECT "identification_number" FROM "TMP_test_275""#,
+                r#"(SELECT "identification_number" FROM "TMP_test_0136""#,
                 r#") as "t8""#,
                 r#"ON ("t3"."id") = ("t8"."identification_number")"#,
                 r#"WHERE ("t3"."id") = (?) and ("t8"."identification_number") = (?)"#
@@ -441,7 +440,7 @@ fn join_linker2_test() {
                 r#""t1"."id", "t1"."sysFrom", "t1"."FIRST_NAME", "t1"."sys_op""#,
                 r#"FROM "test_space" as "t1") as "t1""#,
                 r#"INNER JOIN"#,
-                r#"(SELECT "id1","id2" FROM "TMP_test_87")"#,
+                r#"(SELECT "id1","id2" FROM "TMP_test_0136")"#,
                 r#"as "t2" ON ("t1"."id") = (?)"#
             ),
             vec![Value::from(1_u64)],
@@ -504,7 +503,7 @@ fn join_linker3_test() {
                 r#"SELECT "t2"."id1" FROM"#,
                 r#"(SELECT "test_space"."id" FROM "test_space") as "t1""#,
                 r#"INNER JOIN"#,
-                r#"(SELECT "id1","FIRST_NAME" FROM "TMP_test_69") as "t2""#,
+                r#"(SELECT "id1","FIRST_NAME" FROM "TMP_test_0136") as "t2""#,
                 r#"ON ("t2"."id1") = (?)"#,
             ),
             vec![Value::from(1_u64)],
@@ -592,9 +591,9 @@ fn join_linker4_test() {
                     r#""T1"."id", "T1"."sysFrom", "T1"."FIRST_NAME", "T1"."sys_op""#,
                     r#"FROM "test_space" as "T1") as "T1""#,
                     r#"INNER JOIN"#,
-                    r#"(SELECT "r_id" FROM "TMP_test_148") as "T2""#,
+                    r#"(SELECT "r_id" FROM "TMP_test_0136") as "T2""#,
                     r#"ON ("T1"."id") = ("T2"."r_id")"#,
-                    r#"and ("T1"."FIRST_NAME") = (SELECT "fn" FROM "TMP_test_152")"#,
+                    r#"and ("T1"."FIRST_NAME") = (SELECT "fn" FROM "TMP_test_1136")"#,
                 ),
                 vec![],
             ))),
@@ -608,9 +607,9 @@ fn join_linker4_test() {
                     r#""T1"."id", "T1"."sysFrom", "T1"."FIRST_NAME", "T1"."sys_op""#,
                     r#"FROM "test_space" as "T1") as "T1""#,
                     r#"INNER JOIN"#,
-                    r#"(SELECT "r_id" FROM "TMP_test_148") as "T2""#,
+                    r#"(SELECT "r_id" FROM "TMP_test_0136") as "T2""#,
                     r#"ON ("T1"."id") = ("T2"."r_id")"#,
-                    r#"and ("T1"."FIRST_NAME") = (SELECT "fn" FROM "TMP_test_152")"#,
+                    r#"and ("T1"."FIRST_NAME") = (SELECT "fn" FROM "TMP_test_1136")"#,
                 ),
                 vec![],
             ))),
@@ -686,7 +685,7 @@ on q."f" = "t1"."a""#;
                 "{} {} {} {}",
                 r#"SELECT "t1"."a", "t1"."b", "q"."f", "q"."b" FROM"#,
                 r#"(SELECT "t1"."a", "t1"."b" FROM "t1") as "t1""#,
-                r#"INNER JOIN (SELECT "f","B" FROM "TMP_test_146")"#,
+                r#"INNER JOIN (SELECT "f","B" FROM "TMP_test_1136")"#,
                 r#"as "q" ON ("q"."f") = ("t1"."a")"#,
             ),
             vec![],
@@ -728,7 +727,7 @@ fn dispatch_order_by() {
     expected.rows.extend(vec![vec![
         LuaValue::String("Execute query locally".to_string()),
         LuaValue::String(String::from(PatternWithParams::new(
-            r#"SELECT "id" FROM (SELECT "id" FROM "TMP_test_40") ORDER BY "id""#.to_string(),
+            r#"SELECT "id" FROM (SELECT "id" FROM "TMP_test_0136") ORDER BY "id""#.to_string(),
             vec![],
         ))),
     ]]);
@@ -802,9 +801,9 @@ fn anonymous_col_index_test() {
                     r#""test_space"."sys_op""#,
                     r#"FROM "test_space""#,
                     r#"WHERE ("test_space"."id") in"#,
-                    r#"(SELECT "identification_number" FROM "TMP_test_130")"#,
+                    r#"(SELECT "identification_number" FROM "TMP_test_1136")"#,
                     r#"or ("test_space"."id") in"#,
-                    r#"(SELECT "identification_number" FROM "TMP_test_134")"#,
+                    r#"(SELECT "identification_number" FROM "TMP_test_0136")"#,
                 ),
                 vec![],
             ))),
@@ -821,15 +820,14 @@ fn anonymous_col_index_test() {
                     r#""test_space"."sys_op""#,
                     r#"FROM "test_space""#,
                     r#"WHERE ("test_space"."id") in"#,
-                    r#"(SELECT "identification_number" FROM "TMP_test_130")"#,
+                    r#"(SELECT "identification_number" FROM "TMP_test_1136")"#,
                     r#"or ("test_space"."id") in"#,
-                    r#"(SELECT "identification_number" FROM "TMP_test_134")"#,
+                    r#"(SELECT "identification_number" FROM "TMP_test_0136")"#,
                 ),
                 vec![],
             ))),
         ],
     ]);
-
     assert_eq!(expected, result);
 }
 
@@ -916,7 +914,7 @@ fn virtual_table_23(alias: Option<&str>) -> VirtualTable {
 
 fn get_motion_policy(plan: &Plan, motion_id: NodeId) -> &MotionPolicy {
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         policy
     } else {
         panic!("Expected a motion node");
@@ -997,9 +995,9 @@ fn groupby_linker_test() {
             LuaValue::String(String::from(PatternWithParams::new(
                 format!(
                     "{} {} {}",
-                    r#"SELECT "column_12" as "ii" FROM"#,
-                    r#"(SELECT "id" FROM "TMP_test_45")"#,
-                    r#"GROUP BY "column_12""#,
+                    r#"SELECT "column_764" as "ii" FROM"#,
+                    r#"(SELECT "id" FROM "TMP_test_0136")"#,
+                    r#"GROUP BY "column_764""#,
                 ),
                 vec![],
             ))),
diff --git a/sbroad-core/src/executor/tests/between.rs b/sbroad-core/src/executor/tests/between.rs
index 2678106ee..8de0a3f0d 100644
--- a/sbroad-core/src/executor/tests/between.rs
+++ b/sbroad-core/src/executor/tests/between.rs
@@ -55,7 +55,7 @@ fn between1_test() {
                 "{} {} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
                 r#"WHERE ("t"."identification_number") >= (?)"#,
-                r#"and ("t"."identification_number") <= (SELECT "id" FROM "TMP_test_102")"#,
+                r#"and ("t"."identification_number") <= (SELECT "id" FROM "TMP_test_0136")"#,
             ),
             vec![Value::from(1_u64)],
         ))),
@@ -108,8 +108,8 @@ fn between2_test() {
             format!(
                 "{} {} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
-                r#"WHERE (SELECT "id" FROM "TMP_test_104") >= (?)"#,
-                r#"and (SELECT "id" FROM "TMP_test_104") <= (?)"#,
+                r#"WHERE (SELECT "id" FROM "TMP_test_0136") >= (?)"#,
+                r#"and (SELECT "id" FROM "TMP_test_0136") <= (?)"#,
             ),
             vec![Value::from(1_u64), Value::from(3_u64)],
         ))),
diff --git a/sbroad-core/src/executor/tests/empty_motion.rs b/sbroad-core/src/executor/tests/empty_motion.rs
index 3b4e531a3..8016c3379 100644
--- a/sbroad-core/src/executor/tests/empty_motion.rs
+++ b/sbroad-core/src/executor/tests/empty_motion.rs
@@ -69,14 +69,14 @@ fn empty_motion1_test() {
                 r#"(SELECT "t"."a", "t"."b" FROM"#,
                 r#"(SELECT "t"."a", "t"."b", "t"."c", "t"."d" FROM "t") as "t""#,
                 r#"INNER JOIN"#,
-                r#"(SELECT "g","h" FROM "TMP_test_334") as "t2""#,
+                r#"(SELECT "g","h" FROM "TMP_test_0136") as "t2""#,
                 r#"ON ("t"."a") = ("t2"."g") and ("t"."b") = ("t2"."h")"#,
                 r#"WHERE ("t"."a") = (?)"#,
                 r#"EXCEPT"#,
                 r#"SELECT "t"."a", "t"."b" FROM"#,
                 r#"(SELECT "t"."a", "t"."b", "t"."c", "t"."d" FROM "t") as "t""#,
                 r#"INNER JOIN"#,
-                r#"(SELECT "g","h" FROM "TMP_test_344") as "t2""#,
+                r#"(SELECT "g","h" FROM "TMP_test_1136") as "t2""#,
                 r#"ON ("t"."a") = ("t2"."g") and ("t"."b") = ("t2"."h")"#,
                 r#"WHERE ("t"."a") = (?)) as "Q""#,
             ),
diff --git a/sbroad-core/src/executor/tests/exec_plan.rs b/sbroad-core/src/executor/tests/exec_plan.rs
index 6234727e1..3992cee46 100644
--- a/sbroad-core/src/executor/tests/exec_plan.rs
+++ b/sbroad-core/src/executor/tests/exec_plan.rs
@@ -1,19 +1,19 @@
 use std::rc::Rc;
 
+use engine::mock::TEMPLATE;
 use itertools::Itertools;
 use pretty_assertions::assert_eq;
 use smol_str::SmolStr;
 
 use crate::backend::sql::tree::{OrderedSyntaxNodes, SyntaxPlan};
 use crate::collection;
-use crate::executor::engine::mock::{
-    ReplicasetDispatchInfo, RouterRuntimeMock, VshardMock, TEMPLATE,
-};
+use crate::executor::engine::mock::{ReplicasetDispatchInfo, RouterRuntimeMock, VshardMock};
+use crate::ir::node::{ArenaType, Node136};
 use crate::ir::relation::Type;
 use crate::ir::tests::{column_integer_user_non_null, column_user_non_null};
 use crate::ir::transformation::redistribution::MotionPolicy;
 use crate::ir::tree::Snapshot;
-use crate::ir::{ArenaType, Node, Slice};
+use crate::ir::Slice;
 
 use super::*;
 
@@ -85,7 +85,7 @@ fn exec_plan_subtree_test() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "test_space"."FIRST_NAME" FROM "test_space" WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_20")"#.to_string(),
+            r#"SELECT "test_space"."FIRST_NAME" FROM "test_space" WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_0136")"#.to_string(),
             vec![]
         ));
 }
@@ -133,7 +133,7 @@ fn exec_plan_subtree_two_stage_groupby_test() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "T1"."FIRST_NAME" as "column_12" FROM "test_space" as "T1" GROUP BY "T1"."FIRST_NAME""#
+            r#"SELECT "T1"."FIRST_NAME" as "column_764" FROM "test_space" as "T1" GROUP BY "T1"."FIRST_NAME""#
                 .to_string(),
             vec![]
         )
@@ -144,7 +144,7 @@ fn exec_plan_subtree_two_stage_groupby_test() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "column_12" as "FIRST_NAME" FROM (SELECT "FIRST_NAME" FROM "TMP_test_6") GROUP BY "column_12""#.to_string(),
+            r#"SELECT "column_764" as "FIRST_NAME" FROM (SELECT "FIRST_NAME" FROM "TMP_test_0136") GROUP BY "column_764""#.to_string(),
             vec![]
         ));
 }
@@ -165,15 +165,15 @@ fn exec_plan_subtree_two_stage_groupby_test_2() {
         .unwrap();
     let mut virtual_table = VirtualTable::new();
     virtual_table.add_column(column_user_non_null(
-        SmolStr::from("column_12"),
+        SmolStr::from("column_764"),
         Type::String,
     ));
     virtual_table.add_column(column_user_non_null(
-        SmolStr::from("column_13"),
+        SmolStr::from("column_864"),
         Type::Integer,
     ));
     virtual_table.add_column(column_user_non_null(
-        SmolStr::from("column_14"),
+        SmolStr::from("column_964"),
         Type::Integer,
     ));
     if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id)
@@ -200,8 +200,8 @@ fn exec_plan_subtree_two_stage_groupby_test_2() {
         PatternWithParams::new(
             format!(
                 "{} {} {}",
-                r#"SELECT "T1"."FIRST_NAME" as "column_12", "T1"."sys_op" as "column_13","#,
-                r#""T1"."sysFrom" as "column_14" FROM "test_space" as "T1""#,
+                r#"SELECT "T1"."sysFrom" as "column_964", "T1"."FIRST_NAME" as "column_764","#,
+                r#""T1"."sys_op" as "column_864" FROM "test_space" as "T1""#,
                 r#"GROUP BY "T1"."FIRST_NAME", "T1"."sys_op", "T1"."sysFrom""#,
             ),
             vec![]
@@ -213,11 +213,12 @@ fn exec_plan_subtree_two_stage_groupby_test_2() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            f_sql(
-                r#"SELECT "column_12" as "FIRST_NAME",
-"column_13" as "sys_op", "column_14" as "sysFrom"
-FROM (SELECT "column_12","column_13","column_14" FROM "TMP_test_14")
-GROUP BY "column_12", "column_13", "column_14""#
+            format!(
+                "{} {} {} {}",
+                r#"SELECT "column_764" as "FIRST_NAME", "column_864" as "sys_op","#,
+                r#""column_964" as "sysFrom" FROM"#,
+                r#"(SELECT "column_764","column_864","column_964" FROM "TMP_test_0136")"#,
+                r#"GROUP BY "column_764", "column_864", "column_964""#,
             ),
             vec![]
         )
@@ -278,12 +279,13 @@ fn exec_plan_subtree_aggregates() {
         sql,
         PatternWithParams::new(
             format!(
-                "{} {} {} {} {} {}",
-                r#"SELECT ("T1"."id") * ("T1"."sys_op") as "column_49", "T1"."sys_op" as "column_12","#,
-                r#""T1"."id" as "column_46", group_concat ("T1"."FIRST_NAME", ?) as "group_concat_58", count ("T1"."sysFrom") as "count_37","#,
-                r#"total ("T1"."id") as "total_64","#,
-                r#"min ("T1"."id") as "min_67", count ("T1"."id") as "count_61", max ("T1"."id") as "max_70","#,
-                r#"sum ("T1"."id") as "sum_42" FROM "test_space" as "T1""#,
+                "{} {} {} {} {} {} {}",
+                r#"SELECT "T1"."sys_op" as "column_764", "T1"."id" as "column_2864","#,
+                r#"("T1"."id") * ("T1"."sys_op") as "column_1632", group_concat ("T1"."FIRST_NAME", ?) as "group_concat_496","#,
+                r#"count ("T1"."sysFrom") as "count_096", total ("T1"."id") as "total_696","#,
+                r#"min ("T1"."id") as "min_796", count ("T1"."id") as "count_596","#,
+                r#"max ("T1"."id") as "max_896", sum ("T1"."id") as "sum_196""#,
+                r#"FROM "test_space" as "T1""#,
                 r#"GROUP BY "T1"."sys_op", ("T1"."id") * ("T1"."sys_op"), "T1"."id""#,
             ),
             vec![Value::from("o")]
@@ -297,14 +299,14 @@ fn exec_plan_subtree_aggregates() {
         PatternWithParams::new(
             format!(
                 "{} {} {} {} {} {} {} {}",
-                r#"SELECT ("column_12") || ("column_12") as "col_1","#,
-                r#"("column_12") * (?) + (sum ("count_37")) as "col_2", sum ("sum_42") as "col_3","#,
-                r#"(sum (DISTINCT "column_49")) / (count (DISTINCT "column_46")) as "col_4","#,
-                r#"group_concat ("group_concat_58", ?) as "col_5","#,
-                r#"sum (CAST ("sum_42" as double)) / sum (CAST ("count_61" as double)) as "col_6","#,
-                r#"total ("total_64") as "col_7", min ("min_67") as "col_8", max ("max_70") as "col_9""#,
-                r#"FROM (SELECT "sys_op","sum_42","count_37","sum_49","count_51","group_concat_58","count_61","total_64","min_67","max_70" FROM "TMP_test_70")"#,
-                r#"GROUP BY "column_12""#
+                r#"SELECT ("column_764") || ("column_764") as "col_1","#,
+                r#"("column_764") * (?) + (sum ("count_096")) as "col_2", sum ("sum_196") as "col_3","#,
+                r#"(sum (DISTINCT "column_1632")) / (count (DISTINCT "column_2864")) as "col_4","#,
+                r#"group_concat ("group_concat_496", ?) as "col_5","#,
+                r#"sum (CAST ("sum_196" as double)) / sum (CAST ("count_596" as double)) as "col_6","#,
+                r#"total ("total_696") as "col_7", min ("min_796") as "col_8", max ("max_896") as "col_9""#,
+                r#"FROM (SELECT "sys_op","sum_42","count_37","sum_49","count_51","group_concat_58","count_61","total_64","min_67","max_70" FROM "TMP_test_0136")"#,
+                r#"GROUP BY "column_764""#
             ),
             vec![Value::Unsigned(2), Value::from("o")]
         )
@@ -326,8 +328,8 @@ fn exec_plan_subtree_aggregates_no_groupby() {
         .position(0)
         .unwrap();
     let mut virtual_table = VirtualTable::new();
-    virtual_table.add_column(column_integer_user_non_null(SmolStr::from("column_19")));
-    virtual_table.add_column(column_integer_user_non_null(SmolStr::from("count_13")));
+    virtual_table.add_column(column_integer_user_non_null(SmolStr::from("column_932")));
+    virtual_table.add_column(column_integer_user_non_null(SmolStr::from("count_096")));
     if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id)
     {
         virtual_table.reshard(key, &query.coordinator).unwrap();
@@ -350,7 +352,7 @@ fn exec_plan_subtree_aggregates_no_groupby() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT ("T1"."id") + ("T1"."sysFrom") as "column_19", count ("T1"."sysFrom") as "count_13" FROM "test_space" as "T1" GROUP BY ("T1"."id") + ("T1"."sysFrom")"#.to_string(),
+            r#"SELECT ("T1"."id") + ("T1"."sysFrom") as "column_632", count ("T1"."sysFrom") as "count_096" FROM "test_space" as "T1" GROUP BY ("T1"."id") + ("T1"."sysFrom")"#.to_string(),
             vec![]
         ));
 
@@ -359,7 +361,7 @@ fn exec_plan_subtree_aggregates_no_groupby() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT sum ("count_13") as "col_1", sum (DISTINCT "column_19") as "col_2" FROM (SELECT "column_19","count_13" FROM "TMP_test_12")"#.to_string(),
+            r#"SELECT sum ("count_096") as "col_1", sum (DISTINCT "column_632") as "col_2" FROM (SELECT "column_932","count_096" FROM "TMP_test_0136")"#.to_string(),
             vec![]
         ));
 }
@@ -400,7 +402,7 @@ fn exec_plan_subquery_under_motion_without_alias() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "tid", "sid" FROM (SELECT "test_space"."id" as "tid" FROM "test_space") INNER JOIN (SELECT "identification_number" FROM "TMP_test_28") ON ?"#.to_string(),
+            r#"SELECT "tid", "sid" FROM (SELECT "test_space"."id" as "tid" FROM "test_space") INNER JOIN (SELECT "identification_number" FROM "TMP_test_0136") ON ?"#.to_string(),
             vec![Value::Boolean(true)]
         ));
 }
@@ -441,7 +443,7 @@ fn exec_plan_subquery_under_motion_with_alias() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "tid", "hti"."sid" FROM (SELECT "test_space"."id" as "tid" FROM "test_space") INNER JOIN (SELECT "identification_number" FROM "TMP_test_28") as "hti" ON ?"#.to_string(),
+            r#"SELECT "tid", "hti"."sid" FROM (SELECT "test_space"."id" as "tid" FROM "test_space") INNER JOIN (SELECT "identification_number" FROM "TMP_test_0136") as "hti" ON ?"#.to_string(),
             vec![Value::Boolean(true)]
         ));
 }
@@ -476,7 +478,7 @@ fn exec_plan_motion_under_in_operator() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "test_space"."id" FROM "test_space" WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_20")"#.to_string(),
+            r#"SELECT "test_space"."id" FROM "test_space" WHERE ("test_space"."id") in (SELECT "identification_number" FROM "TMP_test_0136")"#.to_string(),
             vec![]
         ));
 }
@@ -515,7 +517,7 @@ fn exec_plan_motion_under_except() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "test_space"."id" FROM "test_space" EXCEPT SELECT "identification_number" FROM "TMP_test_19""#.to_string(),
+            r#"SELECT "test_space"."id" FROM "test_space" EXCEPT SELECT "identification_number" FROM "TMP_test_0136""#.to_string(),
             vec![]
         ));
 }
@@ -535,7 +537,7 @@ fn exec_plan_subtree_count_asterisk() {
         .position(0)
         .unwrap();
     let mut virtual_table = VirtualTable::new();
-    virtual_table.add_column(column_integer_user_non_null(SmolStr::from("count_13")));
+    virtual_table.add_column(column_integer_user_non_null(SmolStr::from("count_096")));
     if let MotionPolicy::Segment(key) = get_motion_policy(query.exec_plan.get_ir_plan(), motion_id)
     {
         virtual_table.reshard(key, &query.coordinator).unwrap();
@@ -559,7 +561,7 @@ fn exec_plan_subtree_count_asterisk() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT count (*) as "count_13" FROM "test_space""#.to_string(),
+            r#"SELECT count (*) as "count_096" FROM "test_space""#.to_string(),
             vec![]
         )
     );
@@ -569,7 +571,7 @@ fn exec_plan_subtree_count_asterisk() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT sum ("count_13") as "col_1" FROM (SELECT "count_13" FROM "TMP_test_7")"#
+            r#"SELECT sum ("count_096") as "col_1" FROM (SELECT "count_096" FROM "TMP_test_0136")"#
                 .to_string(),
             vec![]
         )
@@ -628,8 +630,8 @@ fn exec_plan_subtree_having() {
         PatternWithParams::new(
             format!(
                 "{} {} {}",
-                r#"SELECT ("T1"."sys_op") * (?) as "column_64", "T1"."sys_op" as "column_12","#,
-                r#"count (("T1"."sys_op") * (?)) as "count_60" FROM "test_space" as "T1""#,
+                r#"SELECT "T1"."sys_op" as "column_764", ("T1"."sys_op") * (?) as "column_2032","#,
+                r#"count (("T1"."sys_op") * (?)) as "count_196" FROM "test_space" as "T1""#,
                 r#"GROUP BY "T1"."sys_op", ("T1"."sys_op") * (?)"#,
             ),
             vec![Value::Unsigned(2), Value::Unsigned(2), Value::Unsigned(2)]
@@ -648,10 +650,10 @@ fn exec_plan_subtree_having() {
         PatternWithParams::new(
             format!(
                 "{} {} {} {}",
-                r#"SELECT ("column_12") || ("column_12") as "col_1","#,
-                r#"(sum ("count_60")) + (count (DISTINCT "column_64")) as "col_2" FROM"#,
-                r#"(SELECT "column_63","column_12","count_58" FROM "TMP_test_22")"#,
-                r#"GROUP BY "column_12" HAVING (sum (DISTINCT "column_64")) > (?)"#
+                r#"SELECT ("column_764") || ("column_764") as "col_1","#,
+                r#"(sum ("count_196")) + (count (DISTINCT "column_2032")) as "col_2" FROM"#,
+                r#"(SELECT "column_63","column_12","count_58" FROM "TMP_test_0136")"#,
+                r#"GROUP BY "column_764" HAVING (sum (DISTINCT "column_2032")) > (?)"#
             ),
             vec![Value::Unsigned(1u64)]
         )
@@ -711,8 +713,8 @@ fn exec_plan_subtree_having_without_groupby() {
         PatternWithParams::new(
             format!(
                 "{} {} {}",
-                r#"SELECT ("T1"."sys_op") * (?) as "column_45","#,
-                r#"count (("T1"."sys_op") * (?)) as "count_41" FROM "test_space" as "T1""#,
+                r#"SELECT ("T1"."sys_op") * (?) as "column_1332","#,
+                r#"count (("T1"."sys_op") * (?)) as "count_196" FROM "test_space" as "T1""#,
                 r#"GROUP BY ("T1"."sys_op") * (?)"#,
             ),
             vec![Value::Unsigned(2), Value::Unsigned(2), Value::Unsigned(2)]
@@ -731,9 +733,9 @@ fn exec_plan_subtree_having_without_groupby() {
         PatternWithParams::new(
             format!(
                 "{} {} {}",
-                r#"SELECT (sum ("count_41")) + (count (DISTINCT "column_45")) as "col_1""#,
-                r#"FROM (SELECT "column_63","column_12","count_58" FROM "TMP_test_14")"#,
-                r#"HAVING (sum (DISTINCT "column_45")) > (?)"#,
+                r#"SELECT (sum ("count_196")) + (count (DISTINCT "column_1332")) as "col_1""#,
+                r#"FROM (SELECT "column_63","column_12","count_58" FROM "TMP_test_0136")"#,
+                r#"HAVING (sum (DISTINCT "column_1332")) > (?)"#,
             ),
             vec![Value::Unsigned(1u64)]
         )
@@ -847,11 +849,11 @@ fn global_union_all2() {
     let (motion_id, _) = sub_plan
         .get_ir_plan()
         .nodes
-        .iter()
+        .iter136()
         .find_position(|n| {
             matches!(
                 n,
-                Node::Relational(Relational::Motion {
+                Node136::Motion(Motion {
                     policy: MotionPolicy::Full,
                     ..
                 })
@@ -862,7 +864,7 @@ fn global_union_all2() {
     let actual_dispatch = coordinator.detailed_dispatch(sub_plan, &buckets);
     let motion_id = NodeId {
         offset: motion_id as u32,
-        arena_type: ArenaType::Default,
+        arena_type: ArenaType::Arena136,
     };
 
     let expected = vec![
@@ -880,7 +882,7 @@ fn global_union_all2() {
         },
         ReplicasetDispatchInfo {
             rs_id: 2,
-            pattern: r#"SELECT "global_t"."a", "global_t"."b" FROM "global_t" WHERE ("global_t"."b") in (SELECT "e" FROM "TMP_test_16") UNION ALL SELECT "t2"."e", "t2"."f" FROM "t2""#.to_string(),
+            pattern: r#"SELECT "global_t"."a", "global_t"."b" FROM "global_t" WHERE ("global_t"."b") in (SELECT "e" FROM "TMP_test_0136") UNION ALL SELECT "t2"."e", "t2"."f" FROM "t2""#.to_string(),
             params: vec![],
             vtables_map: collection!(motion_id => Rc::new(virtual_table)),
         },
@@ -933,7 +935,7 @@ fn global_union_all3() {
     // these tuples must belong to different replicasets
     let tuple1 = vec![Value::Integer(3)];
     let tuple2 = vec![Value::Integer(2929)];
-    groupby_vtable.add_column(column_integer_user_non_null(SmolStr::from("column_51")));
+    groupby_vtable.add_column(column_integer_user_non_null(SmolStr::from("column_3364")));
     groupby_vtable.add_tuple(tuple1.clone());
     groupby_vtable.add_tuple(tuple2.clone());
     if let MotionPolicy::Segment(key) =
@@ -967,11 +969,11 @@ fn global_union_all3() {
     let (groupby_motion_id, _) = sub_plan
         .get_ir_plan()
         .nodes
-        .iter()
+        .iter136()
         .find_position(|n| {
             matches!(
                 n,
-                Node::Relational(Relational::Motion {
+                Node136::Motion(Motion {
                     policy: MotionPolicy::Segment(_),
                     ..
                 })
@@ -981,11 +983,11 @@ fn global_union_all3() {
     let (sq_motion_id, _) = sub_plan
         .get_ir_plan()
         .nodes
-        .iter()
+        .iter136()
         .find_position(|n| {
             matches!(
                 n,
-                Node::Relational(Relational::Motion {
+                Node136::Motion(Motion {
                     policy: MotionPolicy::Full,
                     ..
                 })
@@ -997,24 +999,24 @@ fn global_union_all3() {
 
     let groupby_motion_id = NodeId {
         offset: groupby_motion_id as u32,
-        arena_type: ArenaType::Default,
+        arena_type: ArenaType::Arena136,
     };
 
     let sq_motion_id = NodeId {
         offset: sq_motion_id as u32,
-        arena_type: ArenaType::Default,
+        arena_type: ArenaType::Arena136,
     };
 
     let expected = vec![
         ReplicasetDispatchInfo {
             rs_id: 0,
-            pattern: r#" select cast(null as integer) where false UNION ALL SELECT "column_51" as "f" FROM (SELECT "column_51" FROM "TMP_test_35") GROUP BY "column_51""#.to_string(),
+            pattern: r#" select cast(null as integer) where false UNION ALL SELECT "column_3364" as "f" FROM (SELECT "column_3364" FROM "TMP_test_2136") GROUP BY "column_3364""#.to_string(),
             params: vec![],
             vtables_map: collection!(groupby_motion_id => Rc::new(groupby_vtable1)),
         },
         ReplicasetDispatchInfo {
             rs_id: 1,
-            pattern: r#"SELECT "global_t"."a" FROM "global_t" WHERE ("global_t"."b") in (SELECT "f" FROM "TMP_test_14") UNION ALL SELECT "column_51" as "f" FROM (SELECT "column_51" FROM "TMP_test_35") GROUP BY "column_51""#.to_string(),
+            pattern: r#"SELECT "global_t"."a" FROM "global_t" WHERE ("global_t"."b") in (SELECT "f" FROM "TMP_test_0136") UNION ALL SELECT "column_3364" as "f" FROM (SELECT "column_3364" FROM "TMP_test_2136") GROUP BY "column_3364""#.to_string(),
             params: vec![],
             vtables_map: collection!(sq_motion_id => Rc::new(sq_vtable), groupby_motion_id => Rc::new(groupby_vtable2)),
         },
@@ -1121,11 +1123,12 @@ fn global_except() {
     expected.rows.extend(vec![vec![
         LuaValue::String(format!("Execute query locally")),
         LuaValue::String(String::from(PatternWithParams::new(
-            r#"SELECT "global_t"."a" FROM "global_t" EXCEPT SELECT "e" FROM "TMP_test_47""#.into(),
+            r#"SELECT "global_t"."a" FROM "global_t" EXCEPT SELECT "e" FROM "TMP_test_0136""#
+                .into(),
             vec![],
         ))),
     ]]);
-    assert_eq!(expected, res,)
+    assert_eq!(expected, res)
 }
 
 #[test]
@@ -1172,7 +1175,7 @@ fn exec_plan_order_by() {
     assert_eq!(
         sql,
         PatternWithParams::new(
-            r#"SELECT "identification_number" FROM (SELECT "identification_number" FROM "TMP_test_6") ORDER BY "identification_number""#.to_string(),
+            r#"SELECT "identification_number" FROM (SELECT "identification_number" FROM "TMP_test_0136") ORDER BY "identification_number""#.to_string(),
             vec![]
         ));
 }
diff --git a/sbroad-core/src/executor/tests/not_eq.rs b/sbroad-core/src/executor/tests/not_eq.rs
index 66453735e..5d72e740d 100644
--- a/sbroad-core/src/executor/tests/not_eq.rs
+++ b/sbroad-core/src/executor/tests/not_eq.rs
@@ -93,7 +93,7 @@ fn not_eq2_test() {
             format!(
                 "{} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
-                r#"WHERE ("t"."identification_number") <> (SELECT "id" FROM "TMP_test_79")"#,
+                r#"WHERE ("t"."identification_number") <> (SELECT "id" FROM "TMP_test_0136")"#,
             ),
             vec![],
         ))),
diff --git a/sbroad-core/src/executor/tests/not_in.rs b/sbroad-core/src/executor/tests/not_in.rs
index 4295df354..f89ddd10f 100644
--- a/sbroad-core/src/executor/tests/not_in.rs
+++ b/sbroad-core/src/executor/tests/not_in.rs
@@ -55,7 +55,7 @@ fn not_in1_test() {
             format!(
                 "{} {}",
                 r#"SELECT "t"."identification_number" FROM "hash_testing" as "t""#,
-                r#"WHERE not ("t"."identification_number") in (SELECT "id" FROM "TMP_test_77")"#,
+                r#"WHERE not ("t"."identification_number") in (SELECT "id" FROM "TMP_test_0136")"#,
             ),
             vec![],
         ))),
diff --git a/sbroad-core/src/executor/vtable.rs b/sbroad-core/src/executor/vtable.rs
index 6a83ebe87..69d2d8151 100644
--- a/sbroad-core/src/executor/vtable.rs
+++ b/sbroad-core/src/executor/vtable.rs
@@ -13,8 +13,8 @@ use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::{TupleBuilderCommand, TupleBuilderPattern};
 use crate::executor::protocol::{Binary, EncodedRows, EncodedTables};
 use crate::executor::{bucket::Buckets, Vshard};
-use crate::ir::expression::NodeId;
 use crate::ir::helpers::RepeatableState;
+use crate::ir::node::NodeId;
 use crate::ir::relation::Column;
 use crate::ir::transformation::redistribution::{ColumnPosition, MotionKey, Target};
 use crate::ir::value::{EncodedValue, LuaValue, MsgPackValue, Value};
@@ -231,10 +231,10 @@ impl VirtualTable {
     /// - bucket index is corrupted
     pub fn new_with_buckets(&self, bucket_ids: &[u64]) -> Result<Self, SbroadError> {
         let mut result = Self::new();
-        result.columns = self.columns.clone();
-        result.name = self.name.clone();
+        result.columns.clone_from(&self.columns);
+        result.name.clone_from(&self.name);
 
-        result.primary_key = self.primary_key.clone();
+        result.primary_key.clone_from(&self.primary_key);
         for bucket_id in bucket_ids {
             // If bucket_id is met among those that are present in self.
             if let Some(pointers) = self.get_bucket_index().get(bucket_id) {
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index 337ac62aa..8bcbb6b74 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -26,29 +26,35 @@ use crate::frontend::sql::ast::{
 };
 use crate::frontend::sql::ir::Translation;
 use crate::frontend::Ast;
-use crate::ir::ddl::{AlterSystemType, ColumnDef, Ddl, SetParamScopeType, SetParamValue};
+use crate::ir::ddl::{AlterSystemType, ColumnDef, SetParamScopeType, SetParamValue};
 use crate::ir::ddl::{Language, ParamDef};
 use crate::ir::expression::cast::Type as CastType;
 use crate::ir::expression::{
-    ColumnPositionMap, ColumnWithScan, ColumnsRetrievalSpec, Expression, ExpressionId,
-    FunctionFeature, NodeId, Position, TrimKind,
+    ColumnPositionMap, ColumnWithScan, ColumnsRetrievalSpec, ExpressionId, FunctionFeature,
+    Position, TrimKind,
+};
+use crate::ir::node::expression::{Expression, MutExpression};
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{
+    AlterSystem, AlterUser, BoolExpr, Constant, CountAsterisk, CreateIndex, CreateProc, CreateRole,
+    CreateTable, CreateUser, DropIndex, DropProc, DropRole, DropTable, DropUser, GrantPrivilege,
+    Node, NodeId, Procedure, RenameRoutine, RevokePrivilege, ScanCte, ScanRelation, SetParam,
+    SetTransaction, Trim,
 };
 use crate::ir::operator::{
-    Arithmetic, Bool, ConflictStrategy, JoinKind, OrderByElement, OrderByEntity, OrderByType,
-    Relational, Unary,
+    Arithmetic, Bool, ConflictStrategy, JoinKind, OrderByElement, OrderByEntity, OrderByType, Unary,
 };
 use crate::ir::relation::{Column, ColumnRole, TableKind, Type as RelationType};
-use crate::ir::tree::traversal::{PostOrder, EXPR_CAPACITY};
+use crate::ir::tree::traversal::{LevelNode, PostOrder, EXPR_CAPACITY};
 use crate::ir::value::Value;
-use crate::ir::{Node, OptionKind, OptionParamValue, OptionSpec, Plan};
+use crate::ir::{OptionKind, OptionParamValue, OptionSpec, Plan};
 use crate::otm::child_span;
 
 use crate::errors::Entity::AST;
 use crate::executor::engine::helpers::{normalize_name_from_sql, to_user};
 use crate::ir::acl::AlterOption;
-use crate::ir::acl::{Acl, GrantRevokeType, Privilege};
+use crate::ir::acl::{GrantRevokeType, Privilege};
 use crate::ir::aggregates::AggregateKind;
-use crate::ir::block::Block;
 use crate::ir::expression::NewColumnsSource;
 use crate::ir::helpers::RepeatableState;
 use crate::ir::transformation::redistribution::ColumnPosition;
@@ -189,7 +195,7 @@ fn parse_call_proc<M: Metadata>(
     pairs_map: &mut ParsingPairsMap,
     worker: &mut ExpressionsWorker<M>,
     plan: &mut Plan,
-) -> Result<Block, SbroadError> {
+) -> Result<Procedure, SbroadError> {
     let proc_name_ast_id = node.children.first().expect("Expected to get Proc name");
     let proc_name = parse_identifier(ast, *proc_name_ast_id)?;
 
@@ -216,14 +222,17 @@ fn parse_call_proc<M: Metadata>(
         values.push(plan_value_id);
     }
 
-    let call_proc = Block::Procedure {
+    let call_proc = Procedure {
         name: proc_name,
         values,
     };
     Ok(call_proc)
 }
 
-fn parse_rename_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_rename_proc(
+    ast: &AbstractSyntaxTree,
+    node: &ParseNode,
+) -> Result<RenameRoutine, SbroadError> {
     if node.rule != Rule::RenameProc {
         return Err(SbroadError::Invalid(
             Entity::Type,
@@ -253,7 +262,7 @@ fn parse_rename_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
             _ => panic!("Unexpected node: {child_node:?}"),
         }
     }
-    Ok(Ddl::RenameRoutine {
+    Ok(RenameRoutine {
         old_name,
         new_name,
         params,
@@ -261,7 +270,10 @@ fn parse_rename_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
     })
 }
 
-fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_create_proc(
+    ast: &AbstractSyntaxTree,
+    node: &ParseNode,
+) -> Result<CreateProc, SbroadError> {
     let proc_name_id = node.children.first().expect("Expected to get Proc name");
     let proc_name = parse_identifier(ast, *proc_name_id)?;
 
@@ -291,7 +303,7 @@ fn parse_create_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
             _ => unreachable!("Unexpected node: {child_node:?}"),
         }
     }
-    let create_proc = Ddl::CreateProc {
+    let create_proc = CreateProc {
         name: proc_name,
         params,
         language,
@@ -307,7 +319,7 @@ fn parse_alter_system<M: Metadata>(
     pairs_map: &mut ParsingPairsMap,
     worker: &mut ExpressionsWorker<M>,
     plan: &mut Plan,
-) -> Result<Ddl, SbroadError> {
+) -> Result<AlterSystem, SbroadError> {
     let alter_system_type_node_id = node
         .children
         .first()
@@ -335,7 +347,7 @@ fn parse_alter_system<M: Metadata>(
                 let expr_pair = pairs_map.remove_pair(*param_value_node_id);
                 let expr_plan_node_id = parse_expr(Pairs::single(expr_pair), &[], worker, plan)?;
                 let value_node = plan.get_node(expr_plan_node_id)?;
-                if let Node::Expression(Expression::Constant { value }) = value_node {
+                if let Node::Expression(Expression::Constant(Constant { value })) = value_node {
                     AlterSystemType::AlterSystemSet {
                         param_name,
                         param_value: value.clone(),
@@ -383,7 +395,7 @@ fn parse_alter_system<M: Metadata>(
         None
     };
 
-    Ok(Ddl::AlterSystem {
+    Ok(AlterSystem {
         ty,
         tier_name,
         timeout: get_default_timeout(),
@@ -407,7 +419,7 @@ fn parse_proc_with_optional_params(
     Ok((proc_name, params))
 }
 
-fn parse_drop_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_drop_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<DropProc, SbroadError> {
     let proc_with_optional_params_id = node
         .children
         .first()
@@ -421,7 +433,7 @@ fn parse_drop_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, Sb
         get_default_timeout()
     };
 
-    Ok(Ddl::DropProc {
+    Ok(DropProc {
         name,
         params,
         timeout,
@@ -429,7 +441,10 @@ fn parse_drop_proc(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, Sb
 }
 
 #[allow(clippy::too_many_lines)]
-fn parse_create_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_create_index(
+    ast: &AbstractSyntaxTree,
+    node: &ParseNode,
+) -> Result<CreateIndex, SbroadError> {
     assert_eq!(node.rule, Rule::CreateIndex);
     let mut name = SmolStr::default();
     let mut table_name = SmolStr::default();
@@ -545,7 +560,7 @@ fn parse_create_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
             _ => panic!("Unexpected index rule: {child_node:?}"),
         }
     }
-    let index = Ddl::CreateIndex {
+    let index = CreateIndex {
         name,
         table_name,
         columns,
@@ -564,7 +579,7 @@ fn parse_create_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
     Ok(index)
 }
 
-fn parse_drop_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_drop_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<DropIndex, SbroadError> {
     assert_eq!(node.rule, Rule::DropIndex);
     let mut name = SmolStr::default();
     let mut timeout = get_default_timeout();
@@ -576,12 +591,15 @@ fn parse_drop_index(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, S
             _ => panic!("Unexpected drop index node: {child_node:?}"),
         }
     }
-    Ok(Ddl::DropIndex { name, timeout })
+    Ok(DropIndex { name, timeout })
 }
 
 #[allow(clippy::too_many_lines)]
 #[allow(clippy::uninlined_format_args)]
-fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_create_table(
+    ast: &AbstractSyntaxTree,
+    node: &ParseNode,
+) -> Result<CreateTable, SbroadError> {
     assert_eq!(
         node.rule,
         Rule::CreateTable,
@@ -862,8 +880,7 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
                 Some("global spaces can use only memtx engine".into()),
             ));
         }
-
-        Ddl::CreateTable {
+        CreateTable {
             name: table_name,
             format: columns,
             primary_key: pk_keys,
@@ -873,7 +890,7 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
             tier,
         }
     } else {
-        Ddl::CreateTable {
+        CreateTable {
             name: table_name,
             format: columns,
             primary_key: pk_keys,
@@ -886,7 +903,7 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
     Ok(create_sharded_table)
 }
 
-fn parse_set_param(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, SbroadError> {
+fn parse_set_param(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<SetParam, SbroadError> {
     let mut scope_type = SetParamScopeType::Session;
     let mut param_value = None;
     for child_id in &node.children {
@@ -926,7 +943,7 @@ fn parse_set_param(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, Sb
             _ => panic!("Unexpected rule met under SetParam."),
         }
     }
-    Ok(Ddl::SetParam {
+    Ok(SetParam {
         scope_type,
         param_value: param_value.unwrap(),
         timeout: get_default_timeout(),
@@ -1543,10 +1560,13 @@ where
     }
 
     fn build_columns_map(&mut self, plan: &Plan, rel_id: NodeId) -> Result<(), SbroadError> {
-        if self.column_positions_cache.get(&rel_id).is_none() {
+        if let std::collections::hash_map::Entry::Vacant(e) =
+            self.column_positions_cache.entry(rel_id)
+        {
             let new_map = ColumnPositionMap::new(plan, rel_id)?;
-            self.column_positions_cache.insert(rel_id, new_map);
+            e.insert(new_map);
         }
+
         Ok(())
     }
 
@@ -1707,7 +1727,7 @@ impl ParseExpression {
                 let child_plan_id = child.populate_plan(plan, worker)?;
 
                 let child_expr = plan.get_node(child_plan_id)?;
-                if let Node::Expression(Expression::Bool { op, .. }) = child_expr {
+                if let Node::Expression(Expression::Bool(BoolExpr { op, .. })) = child_expr {
                     // We don't want simple infix comparisons to be covered with parentheses
                     // as soon as it breaks logic of conflicts resolving which currently
                     // work adequately only with ROWs.
@@ -1766,12 +1786,12 @@ impl ParseExpression {
                     Some(p) => Some(p.populate_plan(plan, worker)?),
                     None => None,
                 };
-                let trim_expr = Expression::Trim {
+                let trim_expr = Trim {
                     kind: kind.clone(),
                     pattern,
                     target: target.populate_plan(plan, worker)?,
                 };
-                plan.nodes.push(Node::Expression(trim_expr))
+                plan.nodes.push(trim_expr.into())
             }
             ParseExpression::FinalBetween {
                 is_not,
@@ -1849,18 +1869,18 @@ impl ParseExpression {
                     let right_expr = plan.get_node(right_plan_id)?;
                     matches!(
                         right_expr,
-                        Node::Expression(Expression::Bool { op: Bool::And, .. })
+                        Node::Expression(Expression::Bool(BoolExpr { op: Bool::And, .. }))
                     )
                 };
                 if matches!(op, ParseExpressionInfixOperator::InfixBool(Bool::And))
                     && right_plan_is_and
                 {
                     let right_expr = plan.get_expression_node(right_plan_id)?;
-                    let fixed_left_and_id = if let Expression::Bool {
+                    let fixed_left_and_id = if let Expression::Bool(BoolExpr {
                         op: Bool::And,
                         left,
                         ..
-                    } = right_expr
+                    }) = right_expr
                     {
                         plan.add_cond(left_row_id, Bool::And, *left)?
                     } else {
@@ -1868,11 +1888,11 @@ impl ParseExpression {
                     };
 
                     let right_expr_mut = plan.get_mut_expression_node(right_plan_id)?;
-                    if let Expression::Bool {
+                    if let MutExpression::Bool(BoolExpr {
                         op: Bool::And,
                         left,
                         ..
-                    } = right_expr_mut
+                    }) = right_expr_mut
                     {
                         *left = fixed_left_and_id;
                         return Ok(right_plan_id);
@@ -2144,7 +2164,7 @@ where
                                                     ))
                                                 ));
                                             }
-                                            let count_asterisk_plan_id = plan.nodes.push(Node::Expression(Expression::CountAsterisk));
+                                            let count_asterisk_plan_id = plan.nodes.push(CountAsterisk{}.into());
                                             parse_exprs_args.push(ParseExpression::PlanId { plan_id: count_asterisk_plan_id });
                                         }
                                         Rule::FunctionArgs => {
@@ -2241,9 +2261,7 @@ where
                             Err(e) => return Err(e)
                         };
                         let child = plan.get_relation_node(*plan_left_id)?;
-                        let child_alias_ids = plan.get_expression_node(
-                            child.output()
-                        )?.get_row_list()?;
+                        let child_alias_ids = plan.get_row_list(child.output())?;
                         let child_alias_id = child_alias_ids
                             .get(col_position)
                             .expect("column position is invalid");
@@ -2398,7 +2416,7 @@ where
                     ParseExpression::PlanId { plan_id }
                 }
                 Rule::CountAsterisk => {
-                    let plan_id = plan.nodes.push(Node::Expression(Expression::CountAsterisk));
+                    let plan_id = plan.nodes.push(CountAsterisk{}.into());
                     ParseExpression::PlanId { plan_id }
                 }
                 rule      => unreachable!("Expr::parse expected atomic rule, found {:?}", rule),
@@ -2747,7 +2765,7 @@ impl AbstractSyntaxTree {
 
             let entity = match expr {
                 Node::Expression(expr) => {
-                    if let Expression::Constant {value: Value::Unsigned(index)} = expr {
+                    if let Expression::Constant(Constant {value: Value::Unsigned(index)}) = expr {
                         let index_usize = usize::try_from(*index).map_err(|_| {
                             SbroadError::Invalid(
                                 Entity::Expression,
@@ -2769,8 +2787,7 @@ impl AbstractSyntaxTree {
                         let mut expr_tree =
                             PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, false), EXPR_CAPACITY);
                         let mut reference_met = false;
-                        for level_node in expr_tree.iter(expr_plan_node_id) {
-                            let node_id = level_node.1;
+                        for LevelNode(_, node_id) in expr_tree.iter(expr_plan_node_id) {
                             if let Expression::Reference { .. } = plan.get_expression_node(node_id)? {
                                 reference_met = true;
                                 break;
@@ -2860,7 +2877,7 @@ impl AbstractSyntaxTree {
                         .expect("could not find first child id in scan node");
                     let rel_child_id_plan = map.get(*rel_child_id_ast)?;
                     let rel_child_node = plan.get_relation_node(rel_child_id_plan)?;
-                    if let Relational::ScanSubQuery { .. } = rel_child_node {
+                    if let Relational::ScanSubQuery(_) = rel_child_node {
                         // We want `SubQuery` ids to be used only during expressions parsing.
                         worker.subquery_ids_queue.pop_back();
                     }
@@ -2870,11 +2887,11 @@ impl AbstractSyntaxTree {
                         let alias_name = parse_normalized_identifier(self, *ast_alias_id)?;
                         // CTE scans can have different aliases, so clone the CTE scan node,
                         // preserving its subtree.
-                        if let Relational::ScanCte { child, .. } = rel_child_node {
+                        if let Relational::ScanCte(ScanCte { child, .. }) = rel_child_node {
                             let scan_id = plan.add_cte(*child, alias_name, vec![])?;
                             map.add(id, scan_id);
                         } else {
-                            let scan = plan.get_mut_relation_node(rel_child_id_plan)?;
+                            let mut scan = plan.get_mut_relation_node(rel_child_id_plan)?;
                             scan.set_scan_name(Some(alias_name.to_smolstr()))?;
                         }
                     }
@@ -3094,7 +3111,7 @@ impl AbstractSyntaxTree {
                                         parse_normalized_identifier(self, *alias_ast_node_id)?
                                     } else {
                                         // We don't use `get_expression_node` here, because we may encounter a `Parameter`.
-                                        if let Node::Expression(Expression::Reference { .. }) =
+                                        if let Node::Expression(Expression::Reference(_)) =
                                             plan.get_node(expr_plan_node_id)?
                                         {
                                             let (col_name, _) = worker
@@ -3179,7 +3196,9 @@ impl AbstractSyntaxTree {
                     let plan_scan_id = map.get(*ast_scan_table_id)?;
                     let plan_scan_node = plan.get_relation_node(plan_scan_id)?;
                     let scan_relation =
-                        if let Relational::ScanRelation { relation, .. } = plan_scan_node {
+                        if let Relational::ScanRelation(ScanRelation { relation, .. }) =
+                            plan_scan_node
+                        {
                             relation.clone()
                         } else {
                             unreachable!("Scan expected under Update")
@@ -3305,7 +3324,9 @@ impl AbstractSyntaxTree {
                         Rule::ScanTable => {
                             let plan_scan_id = map.get(*first_child_id)?;
                             let plan_scan_node = plan.get_relation_node(plan_scan_id)?;
-                            let Relational::ScanRelation { relation, .. } = plan_scan_node else {
+                            let Relational::ScanRelation(ScanRelation { relation, .. }) =
+                                plan_scan_node
+                            else {
                                 unreachable!("Scan expected under ScanTable")
                             };
                             (plan_scan_id, relation.clone())
@@ -3318,7 +3339,9 @@ impl AbstractSyntaxTree {
                             let plan_scan_id = map.get(*ast_table_id)?;
                             let plan_scan_node = plan.get_relation_node(plan_scan_id)?;
                             let relation_name =
-                                if let Relational::ScanRelation { relation, .. } = plan_scan_node {
+                                if let Relational::ScanRelation(ScanRelation { relation, .. }) =
+                                    plan_scan_node
+                                {
                                     relation.clone()
                                 } else {
                                     unreachable!("Scan expected under DeleteFilter")
@@ -3493,53 +3516,53 @@ impl AbstractSyntaxTree {
                 }
                 Rule::CallProc => {
                     let call_proc = parse_call_proc(self, node, pairs_map, &mut worker, &mut plan)?;
-                    let plan_id = plan.nodes.push(Node::Block(call_proc));
+                    let plan_id = plan.nodes.push(call_proc.into());
                     map.add(id, plan_id);
                 }
                 Rule::CreateIndex => {
                     let create_index = parse_create_index(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(create_index));
+                    let plan_id = plan.nodes.push(create_index.into());
                     map.add(id, plan_id);
                 }
                 Rule::AlterSystem => {
                     let alter_system =
                         parse_alter_system(self, node, pairs_map, &mut worker, &mut plan)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(alter_system));
+                    let plan_id = plan.nodes.push(alter_system.into());
                     map.add(id, plan_id);
                 }
                 Rule::CreateProc => {
                     let create_proc = parse_create_proc(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(create_proc));
+                    let plan_id = plan.nodes.push(create_proc.into());
                     map.add(id, plan_id);
                 }
                 Rule::CreateTable => {
                     let create_sharded_table = parse_create_table(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(create_sharded_table));
+                    let plan_id = plan.nodes.push(create_sharded_table.into());
                     map.add(id, plan_id);
                 }
                 Rule::GrantPrivilege => {
                     let (grant_type, grantee_name, timeout) = parse_grant_revoke(node, self)?;
-                    let grant_privilege = Acl::GrantPrivilege {
+                    let grant_privilege = GrantPrivilege {
                         grant_type,
                         grantee_name,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(grant_privilege));
+                    let plan_id = plan.nodes.push(grant_privilege.into());
                     map.add(id, plan_id);
                 }
                 Rule::RevokePrivilege => {
                     let (revoke_type, grantee_name, timeout) = parse_grant_revoke(node, self)?;
-                    let revoke_privilege = Acl::RevokePrivilege {
+                    let revoke_privilege = RevokePrivilege {
                         revoke_type,
                         grantee_name,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(revoke_privilege));
+                    let plan_id = plan.nodes.push(revoke_privilege.into());
                     map.add(id, plan_id);
                 }
                 Rule::DropIndex => {
                     let drop_index = parse_drop_index(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(drop_index));
+                    let plan_id = plan.nodes.push(drop_index.into());
                     map.add(id, plan_id);
                 }
                 Rule::DropRole => {
@@ -3553,11 +3576,11 @@ impl AbstractSyntaxTree {
                     if let Some(timeout_child_id) = node.children.get(1) {
                         timeout = get_timeout(self, *timeout_child_id)?;
                     }
-                    let drop_role = Acl::DropRole {
+                    let drop_role = DropRole {
                         name: role_name,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(drop_role));
+                    let plan_id = plan.nodes.push(drop_role.into());
                     map.add(id, plan_id);
                 }
                 Rule::DropTable => {
@@ -3583,21 +3606,21 @@ impl AbstractSyntaxTree {
                             }
                         }
                     }
-                    let drop_table = Ddl::DropTable {
+                    let drop_table = DropTable {
                         name: table_name,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Ddl(drop_table));
+                    let plan_id = plan.nodes.push(drop_table.into());
                     map.add(id, plan_id);
                 }
                 Rule::DropProc => {
                     let drop_proc = parse_drop_proc(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(drop_proc));
+                    let plan_id = plan.nodes.push(drop_proc.into());
                     map.add(id, plan_id);
                 }
                 Rule::RenameProc => {
                     let rename_proc = parse_rename_proc(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(rename_proc));
+                    let plan_id = plan.nodes.push(rename_proc.into());
                     map.add(id, plan_id);
                 }
                 Rule::AlterUser => {
@@ -3663,12 +3686,12 @@ impl AbstractSyntaxTree {
                         timeout = get_timeout(self, *timeout_node_id)?;
                     }
 
-                    let alter_user = Acl::AlterUser {
+                    let alter_user = AlterUser {
                         name: user_name,
                         alter_option,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(alter_user));
+                    let plan_id = plan.nodes.push(alter_user.into());
                     map.add(id, plan_id);
                 }
                 Rule::CreateUser => {
@@ -3718,13 +3741,13 @@ impl AbstractSyntaxTree {
                         }
                     }
 
-                    let create_user = Acl::CreateUser {
+                    let create_user = CreateUser {
                         name: user_name,
                         password,
                         auth_method,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(create_user));
+                    let plan_id = plan.nodes.push(create_user.into());
                     map.add(id, plan_id);
                 }
                 Rule::DropUser => {
@@ -3738,11 +3761,11 @@ impl AbstractSyntaxTree {
                     if let Some(timeout_child_id) = node.children.get(1) {
                         timeout = get_timeout(self, *timeout_child_id)?;
                     }
-                    let drop_user = Acl::DropUser {
+                    let drop_user = DropUser {
                         name: user_name,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(drop_user));
+                    let plan_id = plan.nodes.push(drop_user.into());
                     map.add(id, plan_id);
                 }
                 Rule::CreateRole => {
@@ -3756,23 +3779,23 @@ impl AbstractSyntaxTree {
                     if let Some(timeout_child_id) = node.children.get(1) {
                         timeout = get_timeout(self, *timeout_child_id)?;
                     }
-                    let create_role = Acl::CreateRole {
+                    let create_role = CreateRole {
                         name: role_name,
                         timeout,
                     };
-                    let plan_id = plan.nodes.push(Node::Acl(create_role));
+                    let plan_id = plan.nodes.push(create_role.into());
                     map.add(id, plan_id);
                 }
                 Rule::SetParam => {
                     let set_param_node = parse_set_param(self, node)?;
-                    let plan_id = plan.nodes.push(Node::Ddl(set_param_node));
+                    let plan_id = plan.nodes.push(set_param_node.into());
                     map.add(id, plan_id);
                 }
                 Rule::SetTransaction => {
-                    let set_transaction_node = Ddl::SetTransaction {
+                    let set_transaction_node = SetTransaction {
                         timeout: get_default_timeout(),
                     };
-                    let plan_id = plan.nodes.push(Node::Ddl(set_transaction_node));
+                    let plan_id = plan.nodes.push(set_transaction_node.into());
                     map.add(id, plan_id);
                 }
                 _ => {}
@@ -3836,11 +3859,11 @@ impl Plan {
     /// Used for unification of expression nodes transformations (e.g. dnf).
     fn row(&mut self, expr_id: NodeId) -> Result<NodeId, SbroadError> {
         let row_id = if let Node::Expression(
-            Expression::Reference { .. }
-            | Expression::Constant { .. }
-            | Expression::Cast { .. }
-            | Expression::Concat { .. }
-            | Expression::StableFunction { .. },
+            Expression::Reference(_)
+            | Expression::Constant(_)
+            | Expression::Cast(_)
+            | Expression::Concat(_)
+            | Expression::StableFunction(_),
         ) = self.get_node(expr_id)?
         {
             self.nodes.add_row(vec![expr_id], None)
diff --git a/sbroad-core/src/frontend/sql/ir.rs b/sbroad-core/src/frontend/sql/ir.rs
index 9f437ddfa..96187f2ef 100644
--- a/sbroad-core/src/frontend/sql/ir.rs
+++ b/sbroad-core/src/frontend/sql/ir.rs
@@ -7,14 +7,21 @@ use tarantool::decimal::Decimal;
 
 use crate::errors::{Action, Entity, SbroadError};
 use crate::frontend::sql::ast::Rule;
-use crate::ir::expression::{Expression, NodeId};
 use crate::ir::helpers::RepeatableState;
-use crate::ir::operator::{OrderByElement, OrderByEntity, Relational};
+use crate::ir::node::expression::{ExprOwned, Expression, MutExpression};
+use crate::ir::node::relational::{MutRelational, RelOwned, Relational};
+use crate::ir::node::{
+    Alias, ArenaType, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Constant, Delete, Except,
+    ExprInParentheses, GroupBy, Having, Insert, Intersect, Join, Limit, Motion, MutNode, Node,
+    NodeId, OrderBy, Projection, Reference, Row, ScanCte, ScanRelation, ScanSubQuery, Selection,
+    SizeNode, StableFunction, Trim, UnaryExpr, Union, UnionAll, Update, Values, ValuesRow,
+};
+use crate::ir::operator::{OrderByElement, OrderByEntity};
 use crate::ir::transformation::redistribution::MotionOpcode;
 use crate::ir::tree::traversal::{LevelNode, PostOrder, EXPR_CAPACITY};
 use crate::ir::value::double::Double;
 use crate::ir::value::Value;
-use crate::ir::{ArenaType, Node, Plan};
+use crate::ir::Plan;
 
 use super::Between;
 
@@ -164,44 +171,53 @@ impl Plan {
         let mut set: HashSet<SubQuery, RepeatableState> = HashSet::with_hasher(RepeatableState);
         // Traverse expression trees of the selection and join nodes.
         // Gather all sub-queries in the boolean expressions there.
-        for (offset, node) in self.nodes.iter().enumerate() {
+        for (relational_id, _node) in self.nodes.iter64().enumerate() {
+            let node = self.get_node(NodeId {
+                offset: u32::try_from(relational_id).unwrap(),
+                arena_type: ArenaType::Arena64,
+            })?;
             match node {
                 Node::Relational(
-                    Relational::Selection { filter: tree, .. }
-                    | Relational::Join {
+                    Relational::Selection(Selection { filter: tree, .. })
+                    | Relational::Join(Join {
                         condition: tree, ..
-                    }
-                    | Relational::Having { filter: tree, .. },
+                    })
+                    | Relational::Having(Having { filter: tree, .. }),
                 ) => {
                     let capacity = self.nodes.len();
                     let mut expr_post = PostOrder::with_capacity(
                         |node| self.nodes.expr_iter(node, false),
                         capacity,
                     );
-                    let relational_id = NodeId {
-                        offset: u32::try_from(offset).unwrap(),
-                        arena_type: ArenaType::Default,
-                    };
-                    for level_node in expr_post.iter(*tree) {
-                        let op_id = level_node.1;
+                    for LevelNode(_, op_id) in expr_post.iter(*tree) {
                         let expression_node = self.get_node(op_id)?;
-                        if let Node::Expression(Expression::Bool { left, right, .. }) =
-                            expression_node
+                        if let Node::Expression(Expression::Bool(BoolExpr {
+                            left, right, ..
+                        })) = expression_node
                         {
                             let children = &[*left, *right];
                             for child in children {
                                 if let Node::Relational(Relational::ScanSubQuery { .. }) =
                                     self.get_node(*child)?
                                 {
+                                    let relational_id = NodeId {
+                                        offset: u32::try_from(relational_id).unwrap(),
+                                        arena_type: ArenaType::Arena64,
+                                    };
                                     set.insert(SubQuery::new(relational_id, op_id, *child));
                                 }
                             }
-                        } else if let Node::Expression(Expression::Unary { child, .. }) =
-                            expression_node
+                        } else if let Node::Expression(Expression::Unary(UnaryExpr {
+                            child, ..
+                        })) = expression_node
                         {
                             if let Node::Relational(Relational::ScanSubQuery { .. }) =
                                 self.get_node(*child)?
                             {
+                                let relational_id = NodeId {
+                                    offset: u32::try_from(relational_id).unwrap(),
+                                    arena_type: ArenaType::Arena64,
+                                };
                                 set.insert(SubQuery::new(relational_id, op_id, *child));
                             }
                         }
@@ -222,10 +238,10 @@ impl Plan {
         for sq in set {
             // Append sub-query to relational node if it is not already there (can happen with BETWEEN).
             match self.get_mut_node(sq.relational)? {
-                Node::Relational(
-                    Relational::Selection { children, .. }
-                    | Relational::Join { children, .. }
-                    | Relational::Having { children, .. },
+                MutNode::Relational(
+                    MutRelational::Selection(Selection { children, .. })
+                    | MutRelational::Join(Join { children, .. })
+                    | MutRelational::Having(Having { children, .. }),
                 ) => {
                     // O(n) can become a problem.
                     if !children.contains(&sq.sq) {
@@ -243,9 +259,9 @@ impl Plan {
             // Generate a reference to the sub-query.
             let rel = self.get_relation_node(sq.relational)?;
             let children = match rel {
-                Relational::Join { children, .. }
-                | Relational::Having { children, .. }
-                | Relational::Selection { children, .. } => children.clone(),
+                Relational::Join(Join { children, .. })
+                | Relational::Having(Having { children, .. })
+                | Relational::Selection(Selection { children, .. }) => children.clone(),
                 _ => {
                     return Err(SbroadError::Invalid(
                         Entity::Relational,
@@ -260,11 +276,11 @@ impl Plan {
 
             // Replace sub-query with reference.
             let op = self.get_mut_expression_node(sq.operator)?;
-            if let Expression::Bool {
+            if let MutExpression::Bool(BoolExpr {
                 ref mut left,
                 ref mut right,
                 ..
-            } = op
+            }) = op
             {
                 if *left == sq.sq {
                     *left = row_id;
@@ -277,7 +293,7 @@ impl Plan {
                     ));
                 }
                 replaces.insert(sq.sq, row_id);
-            } else if let Expression::Unary { child, .. } = op {
+            } else if let MutExpression::Unary(UnaryExpr { child, .. }) = op {
                 *child = row_id;
                 replaces.insert(sq.sq, row_id);
             } else {
@@ -312,7 +328,7 @@ impl Plan {
                 self.clone_expr_subtree(between.left_id)?
             };
             let less_eq_expr = self.get_mut_expression_node(between.less_eq_id)?;
-            if let Expression::Bool { ref mut left, .. } = less_eq_expr {
+            if let MutExpression::Bool(BoolExpr { ref mut left, .. }) = less_eq_expr {
                 *left = left_id;
             } else {
                 return Err(SbroadError::Invalid(
@@ -330,62 +346,60 @@ impl Plan {
         subtree.populate_nodes(top_id);
         let nodes = subtree.take_nodes();
         let mut map = CloneExprSubtreeMap::with_capacity(nodes.len());
-        let mut expr_id = NodeId::default();
         for LevelNode(_, id) in nodes {
-            let next_id = self.nodes.next_id(ArenaType::Default);
-            let mut expr = self.get_expression_node(id)?.clone();
+            let mut expr = self.get_mut_expression_node(id)?.get_expr_owned();
             match expr {
-                Expression::Constant { .. }
-                | Expression::Reference { .. }
-                | Expression::CountAsterisk => {}
-                Expression::Alias { ref mut child, .. }
-                | Expression::ExprInParentheses { ref mut child }
-                | Expression::Cast { ref mut child, .. }
-                | Expression::Unary { ref mut child, .. } => map.replace(child),
-                Expression::Bool {
+                ExprOwned::Constant { .. }
+                | ExprOwned::Reference { .. }
+                | ExprOwned::CountAsterisk { .. } => {}
+                ExprOwned::Alias(Alias { ref mut child, .. })
+                | ExprOwned::ExprInParentheses(ExprInParentheses { ref mut child })
+                | ExprOwned::Cast(Cast { ref mut child, .. })
+                | ExprOwned::Unary(UnaryExpr { ref mut child, .. }) => map.replace(child),
+                ExprOwned::Bool(BoolExpr {
                     ref mut left,
                     ref mut right,
                     ..
-                }
-                | Expression::Arithmetic {
+                })
+                | ExprOwned::Arithmetic(ArithmeticExpr {
                     ref mut left,
                     ref mut right,
                     ..
-                }
-                | Expression::Concat {
+                })
+                | ExprOwned::Concat(Concat {
                     ref mut left,
                     ref mut right,
                     ..
-                } => {
+                }) => {
                     map.replace(left);
                     map.replace(right);
                 }
-                Expression::Trim {
+                ExprOwned::Trim(Trim {
                     ref mut pattern,
                     ref mut target,
                     ..
-                } => {
+                }) => {
                     if let Some(pattern) = pattern {
                         map.replace(pattern);
                     }
                     map.replace(target);
                 }
-                Expression::Row {
+                ExprOwned::Row(Row {
                     list: ref mut children,
                     ..
-                }
-                | Expression::StableFunction {
+                })
+                | ExprOwned::StableFunction(StableFunction {
                     ref mut children, ..
-                } => {
+                }) => {
                     for child in children {
                         map.replace(child);
                     }
                 }
-                Expression::Case {
+                ExprOwned::Case(Case {
                     ref mut search_expr,
                     ref mut when_blocks,
                     ref mut else_expr,
-                } => {
+                }) => {
                     if let Some(search_expr) = search_expr {
                         map.replace(search_expr);
                     }
@@ -398,10 +412,10 @@ impl Plan {
                     }
                 }
             }
-            expr_id = self.nodes.push(Node::Expression(expr));
+            let next_id = self.nodes.push(expr.into());
             map.insert(id, next_id);
         }
-        Ok(expr_id)
+        Ok(map.get(top_id))
     }
 }
 
@@ -440,41 +454,41 @@ impl SubtreeCloner {
         Ok(new_list)
     }
 
-    fn clone_expression(&mut self, expr: &Expression) -> Result<Expression, SbroadError> {
-        let mut copied = expr.clone();
+    fn clone_expression(&mut self, expr: &Expression) -> Result<ExprOwned, SbroadError> {
+        let mut copied = expr.get_expr_owned();
 
         // note: all struct fields are listed explicitly (instead of `..`), so that
         // when a new field is added to a struct, this match must
         // be updated, or compilation will fail.
         match &mut copied {
-            Expression::Constant { value: _ }
-            | Expression::Reference {
+            ExprOwned::Constant(Constant { value: _ })
+            | ExprOwned::Reference(Reference {
                 parent: _,
                 targets: _,
                 position: _,
                 col_type: _,
-            }
-            | Expression::CountAsterisk => {}
-            Expression::Alias {
+            })
+            | ExprOwned::CountAsterisk { .. } => {}
+            ExprOwned::Alias(Alias {
                 ref mut child,
                 name: _,
-            }
-            | Expression::ExprInParentheses { ref mut child }
-            | Expression::Cast {
+            })
+            | ExprOwned::ExprInParentheses(ExprInParentheses { ref mut child })
+            | ExprOwned::Cast(Cast {
                 ref mut child,
                 to: _,
-            }
-            | Expression::Unary {
+            })
+            | ExprOwned::Unary(UnaryExpr {
                 ref mut child,
                 op: _,
-            } => {
+            }) => {
                 *child = self.get_new_id(*child)?;
             }
-            Expression::Case {
+            ExprOwned::Case(Case {
                 ref mut search_expr,
                 ref mut when_blocks,
                 ref mut else_expr,
-            } => {
+            }) => {
                 if let Some(search_expr) = search_expr {
                     *search_expr = self.get_new_id(*search_expr)?;
                 }
@@ -486,40 +500,40 @@ impl SubtreeCloner {
                     *else_expr = self.get_new_id(*else_expr)?;
                 }
             }
-            Expression::Bool {
+            ExprOwned::Bool(BoolExpr {
                 ref mut left,
                 ref mut right,
                 op: _,
-            }
-            | Expression::Arithmetic {
+            })
+            | ExprOwned::Arithmetic(ArithmeticExpr {
                 ref mut left,
                 ref mut right,
                 op: _,
-            }
-            | Expression::Concat {
+            })
+            | ExprOwned::Concat(Concat {
                 ref mut left,
                 ref mut right,
-            } => {
+            }) => {
                 *left = self.get_new_id(*left)?;
                 *right = self.get_new_id(*right)?;
             }
-            Expression::Trim {
+            ExprOwned::Trim(Trim {
                 ref mut pattern,
                 ref mut target,
                 ..
-            } => {
+            }) => {
                 if let Some(pattern) = pattern {
                     *pattern = self.get_new_id(*pattern)?;
                 }
                 *target = self.get_new_id(*target)?;
             }
-            Expression::Row {
+            ExprOwned::Row(Row {
                 list: ref mut children,
                 distribution: _,
-            }
-            | Expression::StableFunction {
+            })
+            | ExprOwned::StableFunction(StableFunction {
                 ref mut children, ..
-            } => {
+            }) => {
                 *children = self.copy_list(&*children)?;
             }
         }
@@ -532,8 +546,8 @@ impl SubtreeCloner {
         &mut self,
         old_relational: &Relational,
         id: NodeId,
-    ) -> Result<Relational, SbroadError> {
-        let mut copied = old_relational.clone();
+    ) -> Result<RelOwned, SbroadError> {
+        let mut copied: RelOwned = old_relational.get_rel_owned();
 
         // all relational nodes have output and children list,
         // which must be copied.
@@ -549,101 +563,101 @@ impl SubtreeCloner {
         // when a new field is added to a struct, this match must
         // be updated, or compilation will fail.
         match &mut copied {
-            Relational::Values {
+            RelOwned::Values(Values {
                 output: _,
                 children: _,
-            }
-            | Relational::Projection {
+            })
+            | RelOwned::Projection(Projection {
                 children: _,
                 output: _,
                 is_distinct: _,
-            }
-            | Relational::Insert {
+            })
+            | RelOwned::Insert(Insert {
                 relation: _,
                 columns: _,
                 children: _,
                 output: _,
                 conflict_strategy: _,
-            }
-            | Relational::Update {
+            })
+            | RelOwned::Update(Update {
                 relation: _,
                 children: _,
                 update_columns_map: _,
                 strategy: _,
                 pk_positions: _,
                 output: _,
-            }
-            | Relational::Delete {
+            })
+            | RelOwned::Delete(Delete {
                 relation: _,
                 children: _,
                 output: _,
-            }
-            | Relational::ScanRelation {
+            })
+            | RelOwned::ScanRelation(ScanRelation {
                 alias: _,
                 output: _,
                 relation: _,
-            }
-            | Relational::ScanCte {
+            })
+            | RelOwned::ScanCte(ScanCte {
                 alias: _,
                 output: _,
                 child: _,
-            }
-            | Relational::ScanSubQuery {
+            })
+            | RelOwned::ScanSubQuery(ScanSubQuery {
                 alias: _,
                 children: _,
                 output: _,
-            }
-            | Relational::Except {
+            })
+            | RelOwned::Except(Except {
                 left: _,
                 right: _,
                 output: _,
-            }
-            | Relational::Intersect {
+            })
+            | RelOwned::Intersect(Intersect {
                 left: _,
                 right: _,
                 output: _,
-            }
-            | Relational::Union {
+            })
+            | RelOwned::Union(Union {
                 left: _,
                 right: _,
                 output: _,
-            }
-            | Relational::UnionAll {
+            })
+            | RelOwned::UnionAll(UnionAll {
                 left: _,
                 right: _,
                 output: _,
-            }
-            | Relational::Limit {
+            })
+            | RelOwned::Limit(Limit {
                 limit: _,
                 child: _,
                 output: _,
-            } => {}
-            Relational::Having {
+            }) => {}
+            RelOwned::Having(Having {
                 children: _,
                 output: _,
                 filter,
-            }
-            | Relational::Selection {
+            })
+            | RelOwned::Selection(Selection {
                 children: _,
                 filter,
                 output: _,
-            }
-            | Relational::Join {
+            })
+            | RelOwned::Join(Join {
                 children: _,
                 condition: filter,
                 output: _,
                 kind: _,
-            } => {
+            }) => {
                 *filter = self.get_new_id(*filter)?;
             }
-            Relational::Motion {
+            RelOwned::Motion(Motion {
                 alias: _,
                 children: _,
                 policy: _,
                 program,
                 output: _,
                 is_child_subquery: _,
-            } => {
+            }) => {
                 for op in &mut program.0 {
                     match op {
                         MotionOpcode::RearrangeForShardedUpdate {
@@ -667,19 +681,19 @@ impl SubtreeCloner {
                     }
                 }
             }
-            Relational::GroupBy {
+            RelOwned::GroupBy(GroupBy {
                 children: _,
                 gr_cols,
                 output: _,
                 is_final: _,
-            } => {
+            }) => {
                 *gr_cols = self.copy_list(gr_cols)?;
             }
-            Relational::OrderBy {
+            RelOwned::OrderBy(OrderBy {
                 child: _,
                 order_by_elements,
                 output: _,
-            } => {
+            }) => {
                 let mut new_order_by_elements = Vec::with_capacity(order_by_elements.len());
                 for element in &mut *order_by_elements {
                     let new_entity = match element.entity {
@@ -695,11 +709,11 @@ impl SubtreeCloner {
                 }
                 *order_by_elements = new_order_by_elements;
             }
-            Relational::ValuesRow {
+            RelOwned::ValuesRow(ValuesRow {
                 output: _,
                 data,
                 children: _,
-            } => {
+            }) => {
                 *data = self.get_new_id(*data)?;
             }
         }
@@ -711,7 +725,9 @@ impl SubtreeCloner {
     // This function replaces those references to new nodes.
     fn replace_backward_refs(&self, plan: &mut Plan) -> Result<(), SbroadError> {
         for old_id in &self.nodes_with_backward_references {
-            if let Node::Relational(Relational::Motion { program, .. }) = plan.get_node(*old_id)? {
+            if let Node::Relational(Relational::Motion(Motion { program, .. })) =
+                plan.get_node(*old_id)?
+            {
                 let op_cnt = program.0.len();
                 for idx in 0..op_cnt {
                     let op = plan.get_motion_opcode(*old_id, idx)?;
@@ -719,10 +735,10 @@ impl SubtreeCloner {
                         let new_motion_id = self.get_new_id(*old_id)?;
                         let new_update_id = self.get_new_id(*update_id)?;
 
-                        if let Relational::Motion {
+                        if let MutRelational::Motion(Motion {
                             program: new_program,
                             ..
-                        } = plan.get_mut_relation_node(new_motion_id)?
+                        }) = plan.get_mut_relation_node(new_motion_id)?
                         {
                             if let Some(MotionOpcode::RearrangeForShardedUpdate {
                                 update_id: new_node_update_id,
@@ -750,17 +766,16 @@ impl SubtreeCloner {
         dfs.populate_nodes(top_id);
         let nodes = dfs.take_nodes();
         drop(dfs);
-        for level_node in nodes {
-            let id = level_node.1;
+        for LevelNode(_, id) in nodes {
             let node = plan.get_node(id)?;
-            let new_node = match node {
-                Node::Relational(rel) => Node::Relational(self.clone_relational(rel, id)?),
-                Node::Expression(expr) => Node::Expression(self.clone_expression(expr)?),
+            let new_node: SizeNode = match node {
+                Node::Relational(rel) => self.clone_relational(&rel, id)?.into(),
+                Node::Expression(expr) => self.clone_expression(&expr)?.into(),
                 _ => {
                     return Err(SbroadError::Invalid(
                         Entity::Node,
                         Some(format_smolstr!(
-                            "clone: expected relational or expression on id: {id:?}"
+                            "clone: expected relational or expression on id: {id}"
                         )),
                     ))
                 }
@@ -771,7 +786,7 @@ impl SubtreeCloner {
                 return Err(SbroadError::Invalid(
                     Entity::Plan,
                     Some(format_smolstr!(
-                        "clone: node with id {id:?} was mapped twice: {old_new_id:?}, {new_id:?}"
+                        "clone: node with id {id} was mapped twice: {old_new_id}, {new_id}"
                     )),
                 ));
             }
@@ -786,7 +801,7 @@ impl SubtreeCloner {
                 SbroadError::Invalid(
                     Entity::Plan,
                     Some(format_smolstr!(
-                        "invalid subtree traversal with top: {top_id:?}"
+                        "invalid subtree traversal with top: {top_id}"
                     )),
                 )
             })
diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs
index a4a43cf03..861ee57cd 100644
--- a/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -1,13 +1,13 @@
 use crate::errors::SbroadError;
+use crate::executor::engine::mock::RouterConfigurationMock;
 use crate::frontend::sql::ast::AbstractSyntaxTree;
 use crate::frontend::Ast;
-use crate::ir::expression::NodeId;
-use crate::ir::operator::Relational;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::NodeId;
 use crate::ir::transformation::helpers::{sql_to_ir, sql_to_optimized_ir};
 use crate::ir::tree::traversal::PostOrder;
 use crate::ir::value::Value;
-use crate::ir::Plan;
-use crate::{executor::engine::mock::RouterConfigurationMock, ir::Positions};
+use crate::ir::{Plan, Positions};
 use pretty_assertions::assert_eq;
 use time::{format_description, OffsetDateTime, Time};
 
@@ -827,10 +827,10 @@ fn front_order_by_over_single_distribution_must_not_add_motion() {
         scan
             projection ("id_count"::integer -> "id_count")
                 scan
-                    projection (sum(("count_13"::integer))::decimal -> "id_count")
+                    projection (sum(("count_096"::integer))::decimal -> "id_count")
                         motion [policy: full]
                             scan
-                                projection (count(("test_space"."id"::unsigned))::integer -> "count_13")
+                                projection (count(("test_space"."id"::unsigned))::integer -> "count_096")
                                     scan "test_space"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -964,10 +964,10 @@ fn track_shard_col_pos() {
         let node_id = level_node.1;
         let node = plan.get_relation_node(node_id).unwrap();
         match node {
-            Relational::ScanRelation { .. } | Relational::Selection { .. } => {
+            Relational::ScanRelation(_) | Relational::Selection(_) => {
                 assert_eq!([Some(4_usize), None], plan.get_positions(node_id).unwrap())
             }
-            Relational::Projection { .. } => {
+            Relational::Projection(_) => {
                 assert_eq!([Some(1_usize), None], plan.get_positions(node_id).unwrap())
             }
             _ => {}
@@ -985,7 +985,7 @@ fn track_shard_col_pos() {
     for level_node in dfs.iter(top) {
         let node_id = level_node.1;
         let node = plan.get_relation_node(node_id).unwrap();
-        if let Relational::Join { .. } = node {
+        if let Relational::Join(_) = node {
             assert_eq!(
                 [Some(4_usize), Some(5_usize)],
                 plan.get_positions(node_id).unwrap()
@@ -1008,7 +1008,7 @@ fn track_shard_col_pos() {
     for level_node in dfs.iter(top) {
         let node_id = level_node.1;
         let node = plan.get_relation_node(node_id).unwrap();
-        if let Relational::Join { .. } = node {
+        if let Relational::Join(_) = node {
             assert_eq!([Some(4_usize), None], plan.get_positions(node_id).unwrap());
         }
     }
@@ -1263,11 +1263,11 @@ fn front_sql_groupby() {
     let plan = sql_to_optimized_ir(input, vec![]);
     println!("{}", plan.as_explain().unwrap());
     let expected_explain = String::from(
-        r#"projection ("column_12"::integer -> "identification_number", "column_13"::string -> "product_code")
-    group by ("column_12"::integer, "column_13"::string) output: ("column_12"::integer -> "column_12", "column_13"::string -> "column_13")
-        motion [policy: segment([ref("column_12"), ref("column_13")])]
+        r#"projection ("column_764"::integer -> "identification_number", "column_864"::string -> "product_code")
+    group by ("column_764"::integer, "column_864"::string) output: ("column_764"::integer -> "column_764", "column_864"::string -> "column_864")
+        motion [policy: segment([ref("column_764"), ref("column_864")])]
             scan
-                projection ("hash_testing"."identification_number"::integer -> "column_12", "hash_testing"."product_code"::string -> "column_13")
+                projection ("hash_testing"."identification_number"::integer -> "column_764", "hash_testing"."product_code"::string -> "column_864")
                     group by ("hash_testing"."identification_number"::integer, "hash_testing"."product_code"::string) output: ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op", "hash_testing"."bucket_id"::unsigned -> "bucket_id")
                         scan "hash_testing"
 execution options:
@@ -1289,11 +1289,11 @@ fn front_sql_groupby_less_cols_in_proj() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::integer -> "identification_number")
-    group by ("column_12"::integer, "column_13"::boolean) output: ("column_12"::integer -> "column_12", "column_13"::boolean -> "column_13")
-        motion [policy: segment([ref("column_12"), ref("column_13")])]
+        r#"projection ("column_764"::integer -> "identification_number")
+    group by ("column_764"::integer, "column_864"::boolean) output: ("column_764"::integer -> "column_764", "column_864"::boolean -> "column_864")
+        motion [policy: segment([ref("column_764"), ref("column_864")])]
             scan
-                projection ("hash_testing"."identification_number"::integer -> "column_12", "hash_testing"."product_units"::boolean -> "column_13")
+                projection ("hash_testing"."identification_number"::integer -> "column_764", "hash_testing"."product_units"::boolean -> "column_864")
                     group by ("hash_testing"."identification_number"::integer, "hash_testing"."product_units"::boolean) output: ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op", "hash_testing"."bucket_id"::unsigned -> "bucket_id")
                         scan "hash_testing"
 execution options:
@@ -1316,11 +1316,11 @@ fn front_sql_groupby_union_1() {
 
     let expected_explain = String::from(
         r#"union all
-    projection ("column_12"::integer -> "identification_number")
-        group by ("column_12"::integer) output: ("column_12"::integer -> "column_12")
-            motion [policy: segment([ref("column_12")])]
+    projection ("column_764"::integer -> "identification_number")
+        group by ("column_764"::integer) output: ("column_764"::integer -> "column_764")
+            motion [policy: segment([ref("column_764")])]
                 scan
-                    projection ("hash_testing"."identification_number"::integer -> "column_12")
+                    projection ("hash_testing"."identification_number"::integer -> "column_764")
                         group by ("hash_testing"."identification_number"::integer) output: ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op", "hash_testing"."bucket_id"::unsigned -> "bucket_id")
                             scan "hash_testing"
     projection ("hash_testing"."identification_number"::integer -> "identification_number")
@@ -1351,11 +1351,11 @@ fn front_sql_groupby_union_2() {
     projection ("identification_number"::integer -> "identification_number")
         scan
             union all
-                projection ("column_28"::integer -> "identification_number")
-                    group by ("column_28"::integer) output: ("column_28"::integer -> "column_28")
-                        motion [policy: segment([ref("column_28")])]
+                projection ("column_1764"::integer -> "identification_number")
+                    group by ("column_1764"::integer) output: ("column_1764"::integer -> "column_1764")
+                        motion [policy: segment([ref("column_1764")])]
                             scan
-                                projection ("hash_testing"."identification_number"::integer -> "column_28")
+                                projection ("hash_testing"."identification_number"::integer -> "column_1764")
                                     group by ("hash_testing"."identification_number"::integer) output: ("hash_testing"."identification_number"::integer -> "identification_number", "hash_testing"."product_code"::string -> "product_code", "hash_testing"."product_units"::boolean -> "product_units", "hash_testing"."sys_op"::unsigned -> "sys_op", "hash_testing"."bucket_id"::unsigned -> "bucket_id")
                                         scan "hash_testing"
                 projection ("hash_testing"."identification_number"::integer -> "identification_number")
@@ -1381,11 +1381,11 @@ fn front_sql_groupby_join_1() {
     let plan = sql_to_optimized_ir(input, vec![]);
     println!("{}", plan.as_explain().unwrap());
     let expected_explain = String::from(
-        r#"projection ("column_63"::string -> "product_code", "column_64"::boolean -> "product_units")
-    group by ("column_63"::string, "column_64"::boolean) output: ("column_63"::string -> "column_63", "column_64"::boolean -> "column_64")
-        motion [policy: segment([ref("column_63"), ref("column_64")])]
+        r#"projection ("column_4064"::string -> "product_code", "column_4164"::boolean -> "product_units")
+    group by ("column_4064"::string, "column_4164"::boolean) output: ("column_4064"::string -> "column_4064", "column_4164"::boolean -> "column_4164")
+        motion [policy: segment([ref("column_4064"), ref("column_4164")])]
             scan
-                projection ("t2"."product_code"::string -> "column_63", "t2"."product_units"::boolean -> "column_64")
+                projection ("t2"."product_code"::string -> "column_4064", "t2"."product_units"::boolean -> "column_4164")
                     group by ("t2"."product_code"::string, "t2"."product_units"::boolean) output: ("t2"."product_units"::boolean -> "product_units", "t2"."product_code"::string -> "product_code", "t2"."identification_number"::integer -> "identification_number", "t"."id"::unsigned -> "id")
                         join on ROW("t2"."identification_number"::integer) = ROW("t"."id"::unsigned)
                             scan "t2"
@@ -1473,10 +1473,10 @@ vtable_max_rows = 5000
                 scan "hash_single_testing"
         motion [policy: full]
             scan "t2"
-                projection (sum(("sum_41"::decimal))::decimal -> "id")
+                projection (sum(("sum_096"::decimal))::decimal -> "id")
                     motion [policy: full]
                         scan
-                            projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_41")
+                            projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_096")
                                 scan "test_space"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1496,11 +1496,11 @@ fn front_sql_groupby_insert() {
     let expected_explain = String::from(
         r#"insert "t" on conflict: fail
     motion [policy: segment([value(NULL), ref("d")])]
-        projection ("column_12"::unsigned -> "b", "column_13"::unsigned -> "d")
-            group by ("column_13"::unsigned, "column_12"::unsigned) output: ("column_13"::unsigned -> "column_13", "column_12"::unsigned -> "column_12")
-                motion [policy: segment([ref("column_13"), ref("column_12")])]
+        projection ("column_764"::unsigned -> "b", "column_864"::unsigned -> "d")
+            group by ("column_864"::unsigned, "column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_864"::unsigned -> "column_864")
+                motion [policy: segment([ref("column_864"), ref("column_764")])]
                     scan
-                        projection ("t"."d"::unsigned -> "column_13", "t"."b"::unsigned -> "column_12")
+                        projection ("t"."b"::unsigned -> "column_764", "t"."d"::unsigned -> "column_864")
                             group by ("t"."d"::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")
                                 scan "t"
 execution options:
@@ -1547,11 +1547,11 @@ fn front_sql_aggregates() {
     println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "b", ROW(sum(("count_29"::integer))::decimal) + ROW(sum(("count_31"::integer))::decimal) -> "col_1")
-    group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "count_31"::integer -> "count_31", "count_29"::integer -> "count_29")
-        motion [policy: segment([ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "b", ROW(sum(("count_096"::integer))::decimal) + ROW(sum(("count_196"::integer))::decimal) -> "col_1")
+    group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "count_196"::integer -> "count_196", "count_096"::integer -> "count_096")
+        motion [policy: segment([ref("column_764")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", count(("t"."b"::unsigned))::integer -> "count_31", count(("t"."a"::unsigned))::integer -> "count_29")
+                projection ("t"."b"::unsigned -> "column_764", count(("t"."b"::unsigned))::integer -> "count_196", count(("t"."a"::unsigned))::integer -> "count_096")
                     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:
@@ -1571,10 +1571,10 @@ fn front_sql_avg_aggregate() {
     println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
-        r#"projection (sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::decimal::double))::decimal -> "col_1", avg(distinct ("column_15"::decimal::double))::decimal -> "col_2", ROW(sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::decimal::double))::decimal) * ROW(sum(("sum_13"::decimal::double))::decimal / sum(("count_13"::decimal::double))::decimal) -> "col_3")
+        r#"projection (sum(("sum_096"::decimal::double))::decimal / sum(("count_096"::decimal::double))::decimal -> "col_1", avg(distinct ("column_864"::decimal::double))::decimal -> "col_2", ROW(sum(("sum_096"::decimal::double))::decimal / sum(("count_096"::decimal::double))::decimal) * ROW(sum(("sum_096"::decimal::double))::decimal / sum(("count_096"::decimal::double))::decimal) -> "col_3")
     motion [policy: full]
         scan
-            projection ("t"."b"::unsigned -> "column_15", count(("t"."b"::unsigned))::integer -> "count_13", sum(("t"."b"::unsigned))::decimal -> "sum_13")
+            projection ("t"."b"::unsigned -> "column_864", count(("t"."b"::unsigned))::integer -> "count_096", sum(("t"."b"::unsigned))::decimal -> "sum_096")
                 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:
@@ -1593,10 +1593,10 @@ fn front_sql_total_aggregate() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (total(("total_13"::double))::double -> "col_1", total(distinct ("column_15"::double))::double -> "col_2")
+        r#"projection (total(("total_096"::double))::double -> "col_1", total(distinct ("column_864"::double))::double -> "col_2")
     motion [policy: full]
         scan
-            projection ("t"."b"::unsigned -> "column_15", total(("t"."b"::unsigned))::double -> "total_13")
+            projection ("t"."b"::unsigned -> "column_864", total(("t"."b"::unsigned))::double -> "total_096")
                 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:
@@ -1615,10 +1615,10 @@ fn front_sql_min_aggregate() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (min(("min_13"::unsigned))::scalar -> "col_1", min(distinct ("column_15"::unsigned))::scalar -> "col_2")
+        r#"projection (min(("min_096"::unsigned))::scalar -> "col_1", min(distinct ("column_864"::unsigned))::scalar -> "col_2")
     motion [policy: full]
         scan
-            projection ("t"."b"::unsigned -> "column_15", min(("t"."b"::unsigned))::scalar -> "min_13")
+            projection ("t"."b"::unsigned -> "column_864", min(("t"."b"::unsigned))::scalar -> "min_096")
                 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:
@@ -1637,10 +1637,10 @@ fn front_sql_max_aggregate() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (max(("max_13"::unsigned))::scalar -> "col_1", max(distinct ("column_15"::unsigned))::scalar -> "col_2")
+        r#"projection (max(("max_096"::unsigned))::scalar -> "col_1", max(distinct ("column_864"::unsigned))::scalar -> "col_2")
     motion [policy: full]
         scan
-            projection ("t"."b"::unsigned -> "column_15", max(("t"."b"::unsigned))::scalar -> "max_13")
+            projection ("t"."b"::unsigned -> "column_864", max(("t"."b"::unsigned))::scalar -> "max_096")
                 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:
@@ -1659,10 +1659,10 @@ fn front_sql_group_concat_aggregate() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (group_concat(("group_concat_13"::string))::string -> "col_1", group_concat(distinct ("column_15"::string))::string -> "col_2")
+        r#"projection (group_concat(("group_concat_096"::string))::string -> "col_1", group_concat(distinct ("column_864"::string))::string -> "col_2")
     motion [policy: full]
         scan
-            projection ("test_space"."FIRST_NAME"::string -> "column_15", group_concat(("test_space"."FIRST_NAME"::string))::string -> "group_concat_13")
+            projection ("test_space"."FIRST_NAME"::string -> "column_864", group_concat(("test_space"."FIRST_NAME"::string))::string -> "group_concat_096")
                 group by ("test_space"."FIRST_NAME"::string) output: ("test_space"."id"::unsigned -> "id", "test_space"."sysFrom"::unsigned -> "sysFrom", "test_space"."FIRST_NAME"::string -> "FIRST_NAME", "test_space"."sys_op"::unsigned -> "sys_op", "test_space"."bucket_id"::unsigned -> "bucket_id")
                     scan "test_space"
 execution options:
@@ -1681,10 +1681,10 @@ fn front_sql_group_concat_aggregate2() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (group_concat(("group_concat_14"::string, ' '::string))::string -> "col_1", group_concat(distinct ("column_16"::string))::string -> "col_2")
+        r#"projection (group_concat(("group_concat_096"::string, ' '::string))::string -> "col_1", group_concat(distinct ("column_964"::string))::string -> "col_2")
     motion [policy: full]
         scan
-            projection ("test_space"."FIRST_NAME"::string -> "column_16", group_concat(("test_space"."FIRST_NAME"::string, ' '::string))::string -> "group_concat_14")
+            projection ("test_space"."FIRST_NAME"::string -> "column_964", group_concat(("test_space"."FIRST_NAME"::string, ' '::string))::string -> "group_concat_096")
                 group by ("test_space"."FIRST_NAME"::string) output: ("test_space"."id"::unsigned -> "id", "test_space"."sysFrom"::unsigned -> "sysFrom", "test_space"."FIRST_NAME"::string -> "FIRST_NAME", "test_space"."sys_op"::unsigned -> "sys_op", "test_space"."bucket_id"::unsigned -> "bucket_id")
                     scan "test_space"
 execution options:
@@ -1703,10 +1703,10 @@ fn front_sql_count_asterisk1() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (sum(("count_13"::integer))::decimal -> "col_1", sum(("count_13"::integer))::decimal -> "col_2")
+        r#"projection (sum(("count_096"::integer))::decimal -> "col_1", sum(("count_096"::integer))::decimal -> "col_2")
     motion [policy: full]
         scan
-            projection (count((*::integer))::integer -> "count_13")
+            projection (count((*::integer))::integer -> "count_096")
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1724,11 +1724,11 @@ fn front_sql_count_asterisk2() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (sum(("count_26"::integer))::decimal -> "col_1", "column_12"::unsigned -> "b")
-    group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "count_26"::integer -> "count_26")
-        motion [policy: segment([ref("column_12")])]
+        r#"projection (sum(("count_096"::integer))::decimal -> "col_1", "column_764"::unsigned -> "b")
+    group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "count_096"::integer -> "count_096")
+        motion [policy: segment([ref("column_764")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", count((*::integer))::integer -> "count_26")
+                projection ("t"."b"::unsigned -> "column_764", count((*::integer))::integer -> "count_096")
                     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:
@@ -1764,11 +1764,11 @@ fn front_sql_aggregates_with_subexpressions() {
     println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "b", sum(("count_35"::integer))::decimal -> "col_1", sum(("count_39"::integer))::decimal -> "col_2")
-    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")])]
+        r#"projection ("column_764"::unsigned -> "b", sum(("count_096"::integer))::decimal -> "col_1", sum(("count_296"::integer))::decimal -> "col_2")
+    group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "count_296"::integer -> "count_296", "count_096"::integer -> "count_096")
+        motion [policy: segment([ref("column_764")])]
             scan
-                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")
+                projection ("t"."b"::unsigned -> "column_764", count(("func"(("t"."a"::unsigned))::integer))::integer -> "count_296", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::integer -> "count_096")
                     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:
@@ -1787,11 +1787,11 @@ fn front_sql_aggregates_with_distinct1() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "b", count(distinct ("column_27"::integer))::integer -> "col_1", count(distinct ("column_12"::integer))::integer -> "col_2")
-    group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "column_27"::unsigned -> "column_27")
-        motion [policy: segment([ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "b", count(distinct ("column_1664"::integer))::integer -> "col_1", count(distinct ("column_764"::integer))::integer -> "col_2")
+    group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_1664"::unsigned -> "column_1664")
+        motion [policy: segment([ref("column_764")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", "t"."a"::unsigned -> "column_27")
+                projection ("t"."b"::unsigned -> "column_764", "t"."a"::unsigned -> "column_1664")
                     group by ("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:
@@ -1811,11 +1811,11 @@ fn front_sql_aggregates_with_distinct2() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "b", sum(distinct ("column_34"::decimal))::decimal -> "col_1")
-    group by ("column_12"::unsigned) output: ("column_34"::unsigned -> "column_34", "column_12"::unsigned -> "column_12")
-        motion [policy: segment([ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "b", sum(distinct ("column_1232"::decimal))::decimal -> "col_1")
+    group by ("column_764"::unsigned) output: ("column_1232"::unsigned -> "column_1232", "column_764"::unsigned -> "column_764")
+        motion [policy: segment([ref("column_764")])]
             scan
-                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned) -> "column_34", "t"."b"::unsigned -> "column_12")
+                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned) -> "column_1232", "t"."b"::unsigned -> "column_764")
                     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:
@@ -1834,10 +1834,10 @@ fn front_sql_aggregates_with_distinct3() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (sum(distinct ("column_19"::decimal))::decimal -> "col_1")
+        r#"projection (sum(distinct ("column_632"::decimal))::decimal -> "col_1")
     motion [policy: full]
         scan
-            projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned) -> "column_19")
+            projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) + ROW(3::unsigned) -> "column_632")
                 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:
@@ -1962,12 +1962,12 @@ fn front_sql_pg_style_params3() {
 
     let plan = sql_to_optimized_ir(input, vec![Value::Unsigned(42)]);
     let expected_explain = String::from(
-        r#"projection ("column_31"::unsigned -> "col_1")
-    having ROW(sum(("count_46"::integer))::decimal) > ROW(42::unsigned)
-        group by ("column_31"::unsigned) output: ("column_31"::unsigned -> "column_31", "count_46"::integer -> "count_46")
-            motion [policy: segment([ref("column_31")])]
+        r#"projection ("column_1132"::unsigned -> "col_1")
+    having ROW(sum(("count_096"::integer))::decimal) > ROW(42::unsigned)
+        group by ("column_1132"::unsigned) output: ("column_1132"::unsigned -> "column_1132", "count_096"::integer -> "count_096")
+            motion [policy: segment([ref("column_1132")])]
                 scan
-                    projection (ROW("t"."a"::unsigned) + ROW(42::unsigned) -> "column_31", count(("t"."b"::unsigned))::integer -> "count_46")
+                    projection (ROW("t"."a"::unsigned) + ROW(42::unsigned) -> "column_1132", count(("t"."b"::unsigned))::integer -> "count_096")
                         group by (ROW("t"."a"::unsigned) + ROW(42::unsigned)) output: ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d", "t"."bucket_id"::unsigned -> "bucket_id")
                             selection ROW("t"."a"::unsigned) = ROW(42::unsigned)
                                 scan "t"
@@ -2045,10 +2045,10 @@ fn front_sql_aggregate_without_groupby() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (sum(("sum_20"::decimal))::decimal -> "col_1")
+        r#"projection (sum(("sum_096"::decimal))::decimal -> "col_1")
     motion [policy: full]
         scan
-            projection (sum((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::decimal -> "sum_20")
+            projection (sum((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::decimal -> "sum_096")
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2068,10 +2068,10 @@ fn front_sql_aggregate_without_groupby2() {
     let expected_explain = String::from(
         r#"projection ("t1"."col_1"::integer -> "col_1")
     scan "t1"
-        projection (sum(("count_13"::integer))::decimal -> "col_1")
+        projection (sum(("count_096"::integer))::decimal -> "col_1")
             motion [policy: full]
                 scan
-                    projection (count(("test_space"."id"::unsigned))::integer -> "count_13")
+                    projection (count(("test_space"."id"::unsigned))::integer -> "count_096")
                         scan "test_space"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2092,10 +2092,10 @@ fn front_sql_aggregate_on_aggregate() {
     let expected_explain = String::from(
         r#"projection (max(("t1"."c"::integer))::scalar -> "col_1")
     scan "t1"
-        projection (sum(("count_13"::integer))::decimal -> "c")
+        projection (sum(("count_096"::integer))::decimal -> "c")
             motion [policy: full]
                 scan
-                    projection (count(("test_space"."id"::unsigned))::integer -> "count_13")
+                    projection (count(("test_space"."id"::unsigned))::integer -> "count_096")
                         scan "test_space"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2121,10 +2121,10 @@ fn front_sql_union_single_left() {
     projection ("t"."a"::unsigned -> "a")
         scan "t"
     motion [policy: segment([ref("col_1")])]
-        projection (sum(("sum_29"::decimal))::decimal -> "col_1")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_29")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2148,10 +2148,10 @@ fn front_sql_union_single_right() {
     let expected_explain = String::from(
         r#"union all
     motion [policy: segment([ref("col_1")])]
-        projection (sum(("sum_13"::decimal))::decimal -> "col_1")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_13")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                         scan "t"
     projection ("t"."a"::unsigned -> "a")
         scan "t"
@@ -2177,16 +2177,16 @@ fn front_sql_union_single_both() {
     let expected_explain = String::from(
         r#"union all
     motion [policy: segment([ref("col_1")])]
-        projection (sum(("sum_13"::decimal))::decimal -> "col_1")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_13")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                         scan "t"
     motion [policy: segment([ref("col_1")])]
-        projection (sum(("sum_30"::decimal))::decimal -> "col_1")
+        projection (sum(("sum_196"::decimal))::decimal -> "col_1")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_30")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_196")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2206,10 +2206,10 @@ fn front_sql_insert_single() {
     let expected_explain = String::from(
         r#"insert "t" on conflict: fail
     motion [policy: segment([value(NULL), ref("col_2")])]
-        projection (sum(("sum_13"::decimal))::decimal -> "col_1", sum(("count_16"::integer))::decimal -> "col_2")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1", sum(("count_196"::integer))::decimal -> "col_2")
             motion [policy: full]
                 scan
-                    projection (count(("t"."d"::unsigned))::integer -> "count_16", sum(("t"."b"::unsigned))::decimal -> "sum_13")
+                    projection (count(("t"."d"::unsigned))::integer -> "count_196", sum(("t"."b"::unsigned))::decimal -> "sum_096")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2233,10 +2233,10 @@ fn front_sql_except_single_right() {
     projection ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b")
         scan "t"
     motion [policy: segment([ref("col_1"), ref("col_2")])]
-        projection (sum(("sum_31"::decimal))::decimal -> "col_1", sum(("count_34"::integer))::decimal -> "col_2")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1", sum(("count_196"::integer))::decimal -> "col_2")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_31", count(("t"."b"::unsigned))::integer -> "count_34")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096", count(("t"."b"::unsigned))::integer -> "count_196")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2258,10 +2258,10 @@ vtable_max_rows = 5000
     projection ("t"."b"::unsigned -> "b", "t"."a"::unsigned -> "a")
         scan "t"
     motion [policy: segment([ref("col_2"), ref("col_1")])]
-        projection (sum(("sum_31"::decimal))::decimal -> "col_1", sum(("count_34"::integer))::decimal -> "col_2")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1", sum(("count_196"::integer))::decimal -> "col_2")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_31", count(("t"."b"::unsigned))::integer -> "count_34")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096", count(("t"."b"::unsigned))::integer -> "count_196")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2283,10 +2283,10 @@ fn front_sql_except_single_left() {
     let expected_explain = String::from(
         r#"except
     motion [policy: segment([ref("col_1"), ref("col_2")])]
-        projection (sum(("sum_13"::decimal))::decimal -> "col_1", sum(("count_16"::integer))::decimal -> "col_2")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1", sum(("count_196"::integer))::decimal -> "col_2")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_13", count(("t"."b"::unsigned))::integer -> "count_16")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096", count(("t"."b"::unsigned))::integer -> "count_196")
                         scan "t"
     projection ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b")
         scan "t"
@@ -2311,16 +2311,16 @@ fn front_sql_except_single_both() {
     let expected_explain = String::from(
         r#"except
     motion [policy: segment([ref("col_1")])]
-        projection (sum(("sum_13"::decimal))::decimal -> "col_1", sum(("count_16"::integer))::decimal -> "col_2")
+        projection (sum(("sum_096"::decimal))::decimal -> "col_1", sum(("count_196"::integer))::decimal -> "col_2")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_13", count(("t"."b"::unsigned))::integer -> "count_16")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_096", count(("t"."b"::unsigned))::integer -> "count_196")
                         scan "t"
     motion [policy: segment([ref("col_1")])]
-        projection (sum(("sum_33"::decimal))::decimal -> "col_1", sum(("sum_36"::decimal))::decimal -> "col_2")
+        projection (sum(("sum_296"::decimal))::decimal -> "col_1", sum(("sum_396"::decimal))::decimal -> "col_2")
             motion [policy: full]
                 scan
-                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_33", sum(("t"."b"::unsigned))::decimal -> "sum_36")
+                    projection (sum(("t"."a"::unsigned))::decimal -> "sum_296", sum(("t"."b"::unsigned))::decimal -> "sum_396")
                         scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2339,11 +2339,11 @@ fn front_sql_groupby_expression() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_16"::unsigned -> "col_1")
-    group by ("column_16"::unsigned) output: ("column_16"::unsigned -> "column_16")
-        motion [policy: segment([ref("column_16")])]
+        r#"projection ("column_532"::unsigned -> "col_1")
+    group by ("column_532"::unsigned) output: ("column_532"::unsigned -> "column_532")
+        motion [policy: segment([ref("column_532")])]
             scan
-                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_16")
+                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_532")
                     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:
@@ -2363,11 +2363,11 @@ fn front_sql_groupby_expression2() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_17"::unsigned + ROW(sum(("count_37"::integer))::decimal) -> "col_1")
-    group by ("column_17"::unsigned) output: ("column_17"::unsigned -> "column_17", "count_37"::integer -> "count_37")
-        motion [policy: segment([ref("column_17")])]
+        r#"projection ("column_632"::unsigned + ROW(sum(("count_096"::integer))::decimal) -> "col_1")
+    group by ("column_632"::unsigned) output: ("column_632"::unsigned -> "column_632", "count_096"::integer -> "count_096")
+        motion [policy: segment([ref("column_632")])]
             scan
-                projection ((ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned)) -> "column_17", count(("t"."a"::unsigned))::integer -> "count_37")
+                projection ((ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned)) -> "column_632", count(("t"."a"::unsigned))::integer -> "count_096")
                     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:
@@ -2387,11 +2387,11 @@ fn front_sql_groupby_expression3() {
     let plan = sql_to_optimized_ir(input, vec![]);
     println!("{}", plan.as_explain().unwrap());
     let expected_explain = String::from(
-        r#"projection ("column_16"::unsigned -> "col_1", "column_27"::unsigned * ROW(sum(("sum_59"::decimal))::decimal) / ROW(sum(("count_65"::integer))::decimal) -> "col_2")
-    group by ("column_16"::unsigned, "column_27"::unsigned) output: ("column_16"::unsigned -> "column_16", "column_27"::unsigned -> "column_27", "count_65"::integer -> "count_65", "sum_59"::decimal -> "sum_59")
-        motion [policy: segment([ref("column_16"), ref("column_27")])]
+        r#"projection ("column_532"::unsigned -> "col_1", "column_832"::unsigned * ROW(sum(("sum_096"::decimal))::decimal) / ROW(sum(("count_196"::integer))::decimal) -> "col_2")
+    group by ("column_532"::unsigned, "column_832"::unsigned) output: ("column_532"::unsigned -> "column_532", "column_832"::unsigned -> "column_832", "count_196"::integer -> "count_196", "sum_096"::decimal -> "sum_096")
+        motion [policy: segment([ref("column_532"), ref("column_832")])]
             scan
-                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_16", (ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)) -> "column_27", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned)))::integer -> "count_65", sum((ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)))::decimal -> "sum_59")
+                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_532", (ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)) -> "column_832", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned)))::integer -> "count_196", sum((ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)))::decimal -> "sum_096")
                     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:
@@ -2411,11 +2411,11 @@ fn front_sql_groupby_expression4() {
     let plan = sql_to_optimized_ir(input, vec![]);
     println!("{}", plan.as_explain().unwrap());
     let expected_explain = String::from(
-        r#"projection ("column_16"::unsigned -> "col_1", "column_17"::unsigned -> "a")
-    group by ("column_16"::unsigned, "column_17"::unsigned) output: ("column_16"::unsigned -> "column_16", "column_17"::unsigned -> "column_17")
-        motion [policy: segment([ref("column_16"), ref("column_17")])]
+        r#"projection ("column_532"::unsigned -> "col_1", "column_1164"::unsigned -> "a")
+    group by ("column_532"::unsigned, "column_1164"::unsigned) output: ("column_1164"::unsigned -> "column_1164", "column_532"::unsigned -> "column_532")
+        motion [policy: segment([ref("column_532"), ref("column_1164")])]
             scan
-                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_16", "t"."a"::unsigned -> "column_17")
+                projection ("t"."a"::unsigned -> "column_1164", ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_532")
                     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:
@@ -2440,20 +2440,20 @@ fn front_sql_groupby_with_aggregates() {
         r#"projection ("t1"."a"::unsigned -> "a", "t1"."b"::unsigned -> "b", "t1"."c"::decimal -> "c", "t2"."g"::unsigned -> "g", "t2"."e"::unsigned -> "e", "t2"."f"::decimal -> "f")
     join on ROW("t1"."a"::unsigned, "t2"."g"::unsigned) = ROW("t2"."e"::unsigned, "t1"."b"::unsigned)
         scan "t1"
-            projection ("column_12"::unsigned -> "a", "column_13"::unsigned -> "b", sum(("sum_31"::decimal))::decimal -> "c")
-                group by ("column_13"::unsigned, "column_12"::unsigned) output: ("column_13"::unsigned -> "column_13", "column_12"::unsigned -> "column_12", "sum_31"::decimal -> "sum_31")
-                    motion [policy: segment([ref("column_13"), ref("column_12")])]
+            projection ("column_764"::unsigned -> "a", "column_864"::unsigned -> "b", sum(("sum_096"::decimal))::decimal -> "c")
+                group by ("column_864"::unsigned, "column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_864"::unsigned -> "column_864", "sum_096"::decimal -> "sum_096")
+                    motion [policy: segment([ref("column_864"), ref("column_764")])]
                         scan
-                            projection ("t"."b"::unsigned -> "column_13", "t"."a"::unsigned -> "column_12", sum(("t"."c"::unsigned))::decimal -> "sum_31")
+                            projection ("t"."a"::unsigned -> "column_764", "t"."b"::unsigned -> "column_864", sum(("t"."c"::unsigned))::decimal -> "sum_096")
                                 group by ("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"
         motion [policy: full]
             scan "t2"
-                projection ("column_55"::unsigned -> "g", "column_56"::unsigned -> "e", sum(("sum_74"::decimal))::decimal -> "f")
-                    group by ("column_55"::unsigned, "column_56"::unsigned) output: ("column_55"::unsigned -> "column_55", "column_56"::unsigned -> "column_56", "sum_74"::decimal -> "sum_74")
-                        motion [policy: segment([ref("column_55"), ref("column_56")])]
+                projection ("column_3364"::unsigned -> "g", "column_3464"::unsigned -> "e", sum(("sum_196"::decimal))::decimal -> "f")
+                    group by ("column_3364"::unsigned, "column_3464"::unsigned) output: ("column_3464"::unsigned -> "column_3464", "column_3364"::unsigned -> "column_3364", "sum_196"::decimal -> "sum_196")
+                        motion [policy: segment([ref("column_3364"), ref("column_3464")])]
                             scan
-                                projection ("t2"."g"::unsigned -> "column_55", "t2"."e"::unsigned -> "column_56", sum(("t2"."f"::unsigned))::decimal -> "sum_74")
+                                projection ("t2"."e"::unsigned -> "column_3464", "t2"."g"::unsigned -> "column_3364", sum(("t2"."f"::unsigned))::decimal -> "sum_196")
                                     group by ("t2"."g"::unsigned, "t2"."e"::unsigned) output: ("t2"."e"::unsigned -> "e", "t2"."f"::unsigned -> "f", "t2"."g"::unsigned -> "g", "t2"."h"::unsigned -> "h", "t2"."bucket_id"::unsigned -> "bucket_id")
                                         scan "t2"
 execution options:
@@ -2509,10 +2509,10 @@ 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 (ROW(sum(("sum_14"::decimal))::decimal) / ROW(3::unsigned) -> "a")
+                projection (ROW(sum(("sum_096"::decimal))::decimal) / ROW(3::unsigned) -> "a")
                     motion [policy: full]
                         scan
-                            projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_14")
+                            projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_096")
                                 scan "test_space"
         motion [policy: full]
             scan "t2"
@@ -2544,10 +2544,10 @@ 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 (ROW(sum(("sum_14"::decimal))::decimal) / ROW(3::unsigned) -> "a")
+                projection (ROW(sum(("sum_096"::decimal))::decimal) / ROW(3::unsigned) -> "a")
                     motion [policy: full]
                         scan
-                            projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_14")
+                            projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_096")
                                 scan "test_space"
         motion [policy: full]
             scan "t2"
@@ -2578,16 +2578,16 @@ fn front_sql_left_join_single_both() {
         r#"projection ("t1"."a"::decimal -> "a", "t2"."b"::integer -> "b")
     left join on ROW("t1"."a"::decimal) <> ROW("t2"."b"::integer)
         scan "t1"
-            projection (ROW(sum(("sum_14"::decimal))::decimal) / ROW(3::unsigned) -> "a")
+            projection (ROW(sum(("sum_096"::decimal))::decimal) / ROW(3::unsigned) -> "a")
                 motion [policy: full]
                     scan
-                        projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_14")
+                        projection (sum(("test_space"."id"::unsigned))::decimal -> "sum_096")
                             scan "test_space"
         scan "t2"
-            projection (sum(("count_38"::integer))::decimal -> "b")
+            projection (sum(("count_196"::integer))::decimal -> "b")
                 motion [policy: full]
                     scan
-                        projection (count(("test_space"."id"::unsigned))::integer -> "count_38")
+                        projection (count(("test_space"."id"::unsigned))::integer -> "count_196")
                             scan "test_space"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2638,12 +2638,12 @@ fn front_sql_having1() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "a", sum(("sum_52"::decimal))::decimal -> "col_1")
-    having ROW("column_12"::unsigned) > ROW(1::unsigned) and ROW(sum(distinct ("column_27"::decimal))::decimal) > ROW(1::unsigned)
-        group by ("column_12"::unsigned) output: ("column_27"::unsigned -> "column_27", "column_12"::unsigned -> "column_12", "sum_52"::decimal -> "sum_52")
-            motion [policy: segment([ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "a", sum(("sum_196"::decimal))::decimal -> "col_1")
+    having ROW("column_764"::unsigned) > ROW(1::unsigned) and ROW(sum(distinct ("column_1764"::decimal))::decimal) > ROW(1::unsigned)
+        group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_1764"::unsigned -> "column_1764", "sum_196"::decimal -> "sum_196")
+            motion [policy: segment([ref("column_764")])]
                 scan
-                    projection ("t"."b"::unsigned -> "column_27", "t"."a"::unsigned -> "column_12", sum(("t"."b"::unsigned))::decimal -> "sum_52")
+                    projection ("t"."a"::unsigned -> "column_764", "t"."b"::unsigned -> "column_1764", sum(("t"."b"::unsigned))::decimal -> "sum_196")
                         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")
                             scan "t"
 execution options:
@@ -2663,11 +2663,11 @@ fn front_sql_having2() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection (ROW(sum(("sum_39"::decimal))::decimal) * ROW(count(distinct ("column_38"::integer))::integer) -> "col_1", sum(("sum_39"::decimal))::decimal -> "col_2")
-    having ROW(sum(distinct ("column_38"::decimal))::decimal) > ROW(1::unsigned) and ROW(sum(("sum_39"::decimal))::decimal) > ROW(1::unsigned)
+        r#"projection (ROW(sum(("sum_296"::decimal))::decimal) * ROW(count(distinct ("column_2364"::integer))::integer) -> "col_1", sum(("sum_296"::decimal))::decimal -> "col_2")
+    having ROW(sum(distinct ("column_2364"::decimal))::decimal) > ROW(1::unsigned) and ROW(sum(("sum_296"::decimal))::decimal) > ROW(1::unsigned)
         motion [policy: full]
             scan
-                projection ("t"."b"::unsigned -> "column_38", sum(("t"."a"::unsigned))::decimal -> "sum_39")
+                projection ("t"."b"::unsigned -> "column_2364", sum(("t"."a"::unsigned))::decimal -> "sum_296")
                     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:
@@ -2687,11 +2687,11 @@ fn front_sql_having3() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection (sum(("sum_31"::decimal))::decimal -> "col_1")
-    having ROW(sum(("sum_31"::decimal))::decimal) > ROW(1::unsigned)
+        r#"projection (sum(("sum_196"::decimal))::decimal -> "col_1")
+    having ROW(sum(("sum_196"::decimal))::decimal) > ROW(1::unsigned)
         motion [policy: full]
             scan
-                projection (sum(("t"."a"::unsigned))::decimal -> "sum_31")
+                projection (sum(("t"."a"::unsigned))::decimal -> "sum_196")
                     scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2728,12 +2728,12 @@ fn front_sql_having_with_sq() {
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "sysFrom", sum(distinct ("column_80"::decimal))::decimal -> "sum", count(distinct ("column_80"::integer))::integer -> "count")
-    having ROW($0) > ROW(count(distinct ("column_80"::integer))::integer)
-        group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "column_80"::unsigned -> "column_80")
-            motion [policy: segment([ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "sysFrom", sum(distinct ("column_4964"::decimal))::decimal -> "sum", count(distinct ("column_4964"::integer))::integer -> "count")
+    having ROW($0) > ROW(count(distinct ("column_4964"::integer))::integer)
+        group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_4964"::unsigned -> "column_4964")
+            motion [policy: segment([ref("column_764")])]
                 scan
-                    projection ("test_space"."sysFrom"::unsigned -> "column_12", "test_space"."id"::unsigned -> "column_80")
+                    projection ("test_space"."sysFrom"::unsigned -> "column_764", "test_space"."id"::unsigned -> "column_4964")
                         group by ("test_space"."sysFrom"::unsigned, "test_space"."id"::unsigned) output: ("test_space"."id"::unsigned -> "id", "test_space"."sysFrom"::unsigned -> "sysFrom", "test_space"."FIRST_NAME"::string -> "FIRST_NAME", "test_space"."sys_op"::unsigned -> "sys_op", "test_space"."bucket_id"::unsigned -> "bucket_id")
                             scan "test_space"
 subquery $0:
@@ -2780,12 +2780,12 @@ fn front_sql_having_with_sq_segment_motion() {
     println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "sysFrom", "column_13"::unsigned -> "sys_op", sum(distinct ("column_70"::decimal))::decimal -> "sum", count(distinct ("column_70"::integer))::integer -> "count")
-    having ROW("column_12"::unsigned, "column_13"::unsigned) in ROW($0, $0)
-        group by ("column_13"::unsigned, "column_12"::unsigned) output: ("column_70"::unsigned -> "column_70", "column_13"::unsigned -> "column_13", "column_12"::unsigned -> "column_12")
-            motion [policy: segment([ref("column_13"), ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "sysFrom", "column_864"::unsigned -> "sys_op", sum(distinct ("column_4364"::decimal))::decimal -> "sum", count(distinct ("column_4364"::integer))::integer -> "count")
+    having ROW("column_764"::unsigned, "column_864"::unsigned) in ROW($0, $0)
+        group by ("column_864"::unsigned, "column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_4364"::unsigned -> "column_4364", "column_864"::unsigned -> "column_864")
+            motion [policy: segment([ref("column_864"), ref("column_764")])]
                 scan
-                    projection ("test_space"."id"::unsigned -> "column_70", "test_space"."sys_op"::unsigned -> "column_13", "test_space"."sysFrom"::unsigned -> "column_12")
+                    projection ("test_space"."sysFrom"::unsigned -> "column_764", "test_space"."id"::unsigned -> "column_4364", "test_space"."sys_op"::unsigned -> "column_864")
                         group by ("test_space"."sys_op"::unsigned, "test_space"."sysFrom"::unsigned, "test_space"."id"::unsigned) output: ("test_space"."id"::unsigned -> "id", "test_space"."sysFrom"::unsigned -> "sysFrom", "test_space"."FIRST_NAME"::string -> "FIRST_NAME", "test_space"."sys_op"::unsigned -> "sys_op", "test_space"."bucket_id"::unsigned -> "bucket_id")
                             scan "test_space"
 subquery $0:
@@ -2816,12 +2816,12 @@ fn front_sql_having_with_sq_segment_local_motion() {
     println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
-        r#"projection ("column_12"::unsigned -> "sysFrom", "column_13"::unsigned -> "sys_op", sum(distinct ("column_70"::decimal))::decimal -> "sum", count(distinct ("column_70"::integer))::integer -> "count")
-    having ROW("column_12"::unsigned, "column_13"::unsigned) in ROW($0, $0)
-        group by ("column_13"::unsigned, "column_12"::unsigned) output: ("column_70"::unsigned -> "column_70", "column_13"::unsigned -> "column_13", "column_12"::unsigned -> "column_12")
-            motion [policy: segment([ref("column_13"), ref("column_12")])]
+        r#"projection ("column_764"::unsigned -> "sysFrom", "column_864"::unsigned -> "sys_op", sum(distinct ("column_4364"::decimal))::decimal -> "sum", count(distinct ("column_4364"::integer))::integer -> "count")
+    having ROW("column_764"::unsigned, "column_864"::unsigned) in ROW($0, $0)
+        group by ("column_864"::unsigned, "column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_4364"::unsigned -> "column_4364", "column_864"::unsigned -> "column_864")
+            motion [policy: segment([ref("column_864"), ref("column_764")])]
                 scan
-                    projection ("test_space"."id"::unsigned -> "column_70", "test_space"."sys_op"::unsigned -> "column_13", "test_space"."sysFrom"::unsigned -> "column_12")
+                    projection ("test_space"."sysFrom"::unsigned -> "column_764", "test_space"."id"::unsigned -> "column_4364", "test_space"."sys_op"::unsigned -> "column_864")
                         group by ("test_space"."sys_op"::unsigned, "test_space"."sysFrom"::unsigned, "test_space"."id"::unsigned) output: ("test_space"."id"::unsigned -> "id", "test_space"."sysFrom"::unsigned -> "sysFrom", "test_space"."FIRST_NAME"::string -> "FIRST_NAME", "test_space"."sys_op"::unsigned -> "sys_op", "test_space"."bucket_id"::unsigned -> "bucket_id")
                             scan "test_space"
 subquery $0:
@@ -2847,10 +2847,10 @@ fn front_sql_unique_local_aggregates() {
     println!("{}", plan.as_explain().unwrap());
     // 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", ROW(sum(("sum_13"::decimal))::decimal) + ROW(sum(("count_16"::integer))::decimal) -> "col_3")
+        r#"projection (sum(("sum_096"::decimal))::decimal -> "col_1", sum(("count_196"::integer))::decimal -> "col_2", ROW(sum(("sum_096"::decimal))::decimal) + ROW(sum(("count_196"::integer))::decimal) -> "col_3")
     motion [policy: full]
         scan
-            projection (sum(("t"."a"::unsigned))::decimal -> "sum_13", count(("t"."a"::unsigned))::integer -> "count_16")
+            projection (sum(("t"."a"::unsigned))::decimal -> "sum_096", count(("t"."a"::unsigned))::integer -> "count_196")
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -2871,11 +2871,11 @@ fn front_sql_unique_local_groupings() {
     let plan = sql_to_optimized_ir(input, vec![]);
     // here we must compute only two groupby columns at local stage: a, b
     let expected_explain = String::from(
-        r#"projection (sum(distinct ("column_25"::decimal))::decimal -> "col_1", count(distinct ("column_25"::integer))::integer -> "col_2", count(distinct ("column_12"::integer))::integer -> "col_3")
-    group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "column_25"::unsigned -> "column_25")
-        motion [policy: segment([ref("column_12")])]
+        r#"projection (sum(distinct ("column_1564"::decimal))::decimal -> "col_1", count(distinct ("column_1564"::integer))::integer -> "col_2", count(distinct ("column_764"::integer))::integer -> "col_3")
+    group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "column_1564"::unsigned -> "column_1564")
+        motion [policy: segment([ref("column_764")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", "t"."a"::unsigned -> "column_25")
+                projection ("t"."b"::unsigned -> "column_764", "t"."a"::unsigned -> "column_1564")
                     group by ("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:
@@ -2936,11 +2936,11 @@ fn front_sql_select_distinct() {
     println!("{}", plan.as_explain().unwrap());
     // here we must compute only two groupby columns at local stage: a, b
     let expected_explain = String::from(
-        r#"projection ("column_22"::unsigned -> "a", "column_27"::unsigned -> "col_1")
-    group by ("column_27"::unsigned, "column_22"::unsigned) output: ("column_27"::unsigned -> "column_27", "column_22"::unsigned -> "column_22")
-        motion [policy: segment([ref("column_27"), ref("column_22")])]
+        r#"projection ("column_1464"::unsigned -> "a", "column_832"::unsigned -> "col_1")
+    group by ("column_832"::unsigned, "column_1464"::unsigned) output: ("column_832"::unsigned -> "column_832", "column_1464"::unsigned -> "column_1464")
+        motion [policy: segment([ref("column_832"), ref("column_1464")])]
             scan
-                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_27", "t"."a"::unsigned -> "column_22")
+                projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_832", "t"."a"::unsigned -> "column_1464")
                     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:
@@ -2959,11 +2959,11 @@ fn front_sql_select_distinct_asterisk() {
     let plan = sql_to_optimized_ir(input, vec![]);
     println!("{}", plan.as_explain().unwrap());
     let expected_explain = String::from(
-        r#"projection ("column_23"::unsigned -> "a", "column_24"::unsigned -> "b", "column_25"::unsigned -> "c", "column_26"::unsigned -> "d")
-    group by ("column_24"::unsigned, "column_23"::unsigned, "column_25"::unsigned, "column_26"::unsigned) output: ("column_23"::unsigned -> "column_23", "column_26"::unsigned -> "column_26", "column_25"::unsigned -> "column_25", "column_24"::unsigned -> "column_24")
-        motion [policy: segment([ref("column_24"), ref("column_23"), ref("column_25"), ref("column_26")])]
+        r#"projection ("column_1464"::unsigned -> "a", "column_1564"::unsigned -> "b", "column_1664"::unsigned -> "c", "column_1764"::unsigned -> "d")
+    group by ("column_1564"::unsigned, "column_1464"::unsigned, "column_1664"::unsigned, "column_1764"::unsigned) output: ("column_1464"::unsigned -> "column_1464", "column_1564"::unsigned -> "column_1564", "column_1664"::unsigned -> "column_1664", "column_1764"::unsigned -> "column_1764")
+        motion [policy: segment([ref("column_1564"), ref("column_1464"), ref("column_1664"), ref("column_1764")])]
             scan
-                projection ("t"."a"::unsigned -> "column_23", "t"."d"::unsigned -> "column_26", "t"."c"::unsigned -> "column_25", "t"."b"::unsigned -> "column_24")
+                projection ("t"."a"::unsigned -> "column_1464", "t"."b"::unsigned -> "column_1564", "t"."c"::unsigned -> "column_1664", "t"."d"::unsigned -> "column_1764")
                     group by ("t"."b"::unsigned, "t"."a"::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")
                         scan "t"
 execution options:
@@ -2998,11 +2998,11 @@ fn front_sql_select_distinct_with_aggr() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection (sum(("sum_26"::decimal))::decimal -> "col_1", "column_12"::unsigned -> "b")
-    group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "sum_26"::decimal -> "sum_26")
-        motion [policy: segment([ref("column_12")])]
+        r#"projection (sum(("sum_096"::decimal))::decimal -> "col_1", "column_764"::unsigned -> "b")
+    group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "sum_096"::decimal -> "sum_096")
+        motion [policy: segment([ref("column_764")])]
             scan
-                projection ("t"."b"::unsigned -> "column_12", sum(("t"."a"::unsigned))::decimal -> "sum_26")
+                projection ("t"."b"::unsigned -> "column_764", sum(("t"."a"::unsigned))::decimal -> "sum_096")
                     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:
@@ -3020,10 +3020,10 @@ fn front_sql_select_distinct_with_aggr2() {
 
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
-        r#"projection (sum(("sum_13"::decimal))::decimal -> "col_1")
+        r#"projection (sum(("sum_096"::decimal))::decimal -> "col_1")
     motion [policy: full]
         scan
-            projection (sum(("t"."a"::unsigned))::decimal -> "sum_13")
+            projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                 scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -3380,10 +3380,10 @@ fn front_sql_update6() {
 subquery $0:
 motion [policy: full]
                     scan
-                        projection (sum(("sum_17"::decimal))::decimal -> "s")
+                        projection (sum(("sum_096"::decimal))::decimal -> "s")
                             motion [policy: full]
                                 scan
-                                    projection (sum(("t3"."b"::integer))::decimal -> "sum_17")
+                                    projection (sum(("t3"."b"::integer))::decimal -> "sum_096")
                                         scan "t3"
 execution options:
 sql_vdbe_max_steps = 45000
diff --git a/sbroad-core/src/frontend/sql/ir/tests/ddl.rs b/sbroad-core/src/frontend/sql/ir/tests/ddl.rs
index 405e766d7..128101118 100644
--- a/sbroad-core/src/frontend/sql/ir/tests/ddl.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests/ddl.rs
@@ -1,14 +1,14 @@
-use crate::frontend::Ast;
+use crate::{
+    frontend::Ast,
+    ir::node::{ddl::Ddl, CreateTable},
+};
 use pretty_assertions::assert_eq;
 use smol_str::{SmolStr, ToSmolStr};
 
 use crate::{
     executor::engine::mock::RouterConfigurationMock,
     frontend::sql::ast::AbstractSyntaxTree,
-    ir::{
-        ddl::{ColumnDef, Ddl},
-        relation::Type,
-    },
+    ir::{ddl::ColumnDef, relation::Type},
 };
 
 #[test]
@@ -20,11 +20,11 @@ fn infer_not_null_on_pk1() {
     let top_id = plan.get_top().unwrap();
     let top_node = plan.get_ddl_node(top_id).unwrap();
 
-    let Ddl::CreateTable {
+    let Ddl::CreateTable(CreateTable {
         format,
         primary_key,
         ..
-    } = top_node
+    }) = top_node
     else {
         panic!("expected create table")
     };
@@ -51,11 +51,11 @@ fn infer_not_null_on_pk2() {
     let top_id = plan.get_top().unwrap();
     let top_node = plan.get_ddl_node(top_id).unwrap();
 
-    let Ddl::CreateTable {
+    let Ddl::CreateTable(CreateTable {
         format,
         primary_key,
         ..
-    } = top_node
+    }) = top_node
     else {
         panic!("expected create table")
     };
@@ -107,7 +107,7 @@ fn infer_sk_from_pk() {
     let top_id = plan.get_top().unwrap();
     let top_node = plan.get_ddl_node(top_id).unwrap();
 
-    let Ddl::CreateTable { sharding_key, .. } = top_node else {
+    let Ddl::CreateTable(CreateTable { sharding_key, .. }) = top_node else {
         panic!("expected create table")
     };
 
diff --git a/sbroad-core/src/frontend/sql/ir/tests/global.rs b/sbroad-core/src/frontend/sql/ir/tests/global.rs
index e067aed43..ecae11c8a 100644
--- a/sbroad-core/src/frontend/sql/ir/tests/global.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests/global.rs
@@ -1,10 +1,10 @@
 use crate::ir::distribution::Distribution;
-use crate::ir::expression::NodeId;
-use crate::ir::operator::Relational;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{Node, NodeId};
 use crate::ir::transformation::helpers::sql_to_optimized_ir;
 use crate::ir::tree::traversal::{FilterFn, LevelNode, PostOrderWithFilter, REL_CAPACITY};
 use crate::ir::value::Value;
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 use pretty_assertions::assert_eq;
 
 #[derive(PartialEq, Eq, Debug)]
@@ -48,10 +48,8 @@ fn check_distributions(
         nodes.len(),
         "different number of nodes"
     );
-    for (level_node, expected) in nodes.iter().zip(expected_distributions.iter()) {
-        let level = level_node.0;
-        let id = level_node.1;
-        let actual: DistMock = plan.get_rel_distribution(id).unwrap().into();
+    for (LevelNode(level, id), expected) in nodes.iter().zip(expected_distributions.iter()) {
+        let actual: DistMock = plan.get_rel_distribution(*id).unwrap().into();
         assert_eq!(
             expected, &actual,
             "wrong distribution for node ({id:?}) at level {level}"
@@ -63,7 +61,7 @@ fn check_selection_dist(plan: &Plan, expected_dist: DistMock) {
     let filter = |id: NodeId| -> bool {
         matches!(
             plan.get_node(id),
-            Ok(Node::Relational(Relational::Selection { .. }))
+            Ok(Node::Relational(Relational::Selection(_)))
         )
     };
     let nodes = collect_relational(plan, Box::new(filter));
@@ -93,10 +91,10 @@ motion [policy: full]
                     scan "t"
 subquery $1:
 scan
-            projection (sum(("sum_39"::decimal))::decimal -> "col_1")
+            projection (sum(("sum_096"::decimal))::decimal -> "col_1")
                 motion [policy: full]
                     scan
-                        projection (sum(("t"."a"::unsigned))::decimal -> "sum_39")
+                        projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                             scan "t"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -125,10 +123,10 @@ fn front_sql_global_tbl_multiple_sqs1() {
         scan "global_t"
 subquery $0:
 scan
-            projection (sum(("sum_43"::decimal))::decimal -> "col_1")
+            projection (sum(("sum_096"::decimal))::decimal -> "col_1")
                 motion [policy: full]
                     scan
-                        projection (sum(("t"."a"::unsigned))::decimal -> "sum_43")
+                        projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                             scan "t"
 subquery $1:
 scan
@@ -163,10 +161,10 @@ fn front_sql_global_tbl_multiple_sqs2() {
         scan "global_t"
 subquery $0:
 scan
-            projection (sum(("sum_43"::decimal))::decimal -> "col_1")
+            projection (sum(("sum_096"::decimal))::decimal -> "col_1")
                 motion [policy: full]
                     scan
-                        projection (sum(("t"."a"::unsigned))::decimal -> "sum_43")
+                        projection (sum(("t"."a"::unsigned))::decimal -> "sum_096")
                             scan "t"
 subquery $1:
 motion [policy: full]
@@ -366,7 +364,7 @@ fn front_sql_global_tbl_sq7() {
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
         r#"projection ("t"."a"::unsigned -> "a", "t2"."f"::unsigned -> "f")
-    join on ROW("t"."a"::unsigned, "t"."b"::unsigned) = ROW("t2"."e"::unsigned, "t2"."f"::unsigned) or ROW("t"."c"::unsigned) in ROW($1) or not ROW("t"."d"::unsigned) in ROW($0)
+    join on ROW("t"."a"::unsigned, "t"."b"::unsigned) = ROW("t2"."e"::unsigned, "t2"."f"::unsigned) or ROW("t"."c"::unsigned) in ROW($0) or not ROW("t"."d"::unsigned) in ROW($1)
         scan "t"
             projection ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b", "t"."c"::unsigned -> "c", "t"."d"::unsigned -> "d")
                 scan "t"
@@ -392,10 +390,7 @@ vtable_max_rows = 5000
 
 fn check_join_dist(plan: &Plan, expected_distributions: &[DistMock]) {
     let filter = |id: NodeId| -> bool {
-        matches!(
-            plan.get_node(id),
-            Ok(Node::Relational(Relational::Join { .. }))
-        )
+        matches!(plan.get_node(id), Ok(Node::Relational(Relational::Join(_))))
     };
     let nodes = collect_relational(plan, Box::new(filter));
     check_distributions(plan, &nodes, expected_distributions);
@@ -500,10 +495,10 @@ fn front_sql_global_join4() {
         r#"projection ("s"."e"::decimal -> "e")
     left join on true::boolean
         scan "s"
-            projection (sum(("sum_13"::decimal))::decimal -> "e")
+            projection (sum(("sum_096"::decimal))::decimal -> "e")
                 motion [policy: full]
                     scan
-                        projection (sum(("t2"."e"::unsigned))::decimal -> "sum_13")
+                        projection (sum(("t2"."e"::unsigned))::decimal -> "sum_096")
                             scan "t2"
         scan "global_t"
             projection ("global_t"."a"::integer -> "a", "global_t"."b"::integer -> "b")
@@ -534,10 +529,10 @@ fn front_sql_global_join5() {
             projection ("global_t"."a"::integer -> "a", "global_t"."b"::integer -> "b")
                 scan "global_t"
         scan "s"
-            projection (sum(("sum_19"::decimal))::decimal -> "e")
+            projection (sum(("sum_096"::decimal))::decimal -> "e")
                 motion [policy: full]
                     scan
-                        projection (sum(("t2"."e"::unsigned))::decimal -> "sum_19")
+                        projection (sum(("t2"."e"::unsigned))::decimal -> "sum_096")
                             scan "t2"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -819,12 +814,12 @@ fn front_sql_global_aggregate5() {
     println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
-        r#"projection ("column_44"::integer -> "col_1", sum(("sum_70"::decimal))::decimal -> "col_2")
-    having ROW(sum(("sum_53"::decimal::double))::decimal / sum(("count_53"::decimal::double))::decimal) > ROW(3::unsigned)
-        group by ("column_44"::integer) output: ("column_44"::integer -> "column_44", "sum_70"::decimal -> "sum_70", "sum_53"::decimal -> "sum_53", "count_53"::integer -> "count_53")
-            motion [policy: segment([ref("column_44")])]
+        r#"projection ("column_1432"::integer -> "col_1", sum(("sum_196"::decimal))::decimal -> "col_2")
+    having ROW(sum(("sum_096"::decimal::double))::decimal / sum(("count_096"::decimal::double))::decimal) > ROW(3::unsigned)
+        group by ("column_1432"::integer) output: ("column_1432"::integer -> "column_1432", "sum_196"::decimal -> "sum_196", "sum_096"::decimal -> "sum_096", "count_096"::integer -> "count_096")
+            motion [policy: segment([ref("column_1432")])]
                 scan
-                    projection (ROW("global_t"."b"::integer) + ROW("global_t"."a"::integer) -> "column_44", sum(("global_t"."a"::integer))::decimal -> "sum_70", sum(("global_t"."b"::integer))::decimal -> "sum_53", count(("global_t"."b"::integer))::integer -> "count_53")
+                    projection (ROW("global_t"."b"::integer) + ROW("global_t"."a"::integer) -> "column_1432", sum(("global_t"."a"::integer))::decimal -> "sum_196", sum(("global_t"."b"::integer))::decimal -> "sum_096", count(("global_t"."b"::integer))::integer -> "count_096")
                         group by (ROW("global_t"."b"::integer) + ROW("global_t"."a"::integer)) output: ("global_t"."a"::integer -> "a", "global_t"."b"::integer -> "b")
                             selection ROW("global_t"."a"::integer, "global_t"."b"::integer) in ROW($0, $0)
                                 scan "global_t"
@@ -1072,10 +1067,10 @@ fn front_sql_global_union_all3() {
                 projection ("global_t"."a"::integer -> "a")
                     scan "global_t"
                 motion [policy: segment([ref("col_1")])]
-                    projection (sum(("sum_23"::decimal))::decimal -> "col_1")
+                    projection (sum(("sum_096"::decimal))::decimal -> "col_1")
                         motion [policy: full]
                             scan
-                                projection (sum(("t2"."e"::unsigned))::decimal -> "sum_23")
+                                projection (sum(("t2"."e"::unsigned))::decimal -> "sum_096")
                                     scan "t2"
     motion [policy: local]
         projection ("global_t"."b"::integer -> "b")
@@ -1182,10 +1177,10 @@ fn front_sql_global_union2() {
         projection ("global_t"."a"::integer -> "a")
             scan "global_t"
         motion [policy: segment([ref("col_1")])]
-            projection (sum(("sum_23"::decimal))::decimal -> "col_1")
+            projection (sum(("sum_096"::decimal))::decimal -> "col_1")
                 motion [policy: full]
                     scan
-                        projection (sum(("t2"."e"::unsigned))::decimal -> "sum_23")
+                        projection (sum(("t2"."e"::unsigned))::decimal -> "sum_096")
                             scan "t2"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1332,10 +1327,10 @@ fn check_plan_except_global_vs_single() {
         r#"except
     projection ("global_t"."a"::integer -> "a")
         scan "global_t"
-    projection (sum(("sum_23"::decimal))::decimal -> "col_1")
+    projection (sum(("sum_096"::decimal))::decimal -> "col_1")
         motion [policy: full]
             scan
-                projection (sum(("t2"."e"::unsigned))::decimal -> "sum_23")
+                projection (sum(("t2"."e"::unsigned))::decimal -> "sum_096")
                     scan "t2"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -1357,10 +1352,10 @@ fn check_plan_except_single_vs_global() {
 
     let expected_explain = String::from(
         r#"except
-    projection (sum(("sum_13"::decimal))::decimal -> "col_1")
+    projection (sum(("sum_096"::decimal))::decimal -> "col_1")
         motion [policy: full]
             scan
-                projection (sum(("t2"."e"::unsigned))::decimal -> "sum_13")
+                projection (sum(("t2"."e"::unsigned))::decimal -> "sum_096")
                     scan "t2"
     projection ("global_t"."a"::integer -> "a")
         scan "global_t"
diff --git a/sbroad-core/src/frontend/sql/ir/tests/limit.rs b/sbroad-core/src/frontend/sql/ir/tests/limit.rs
index 1787f244c..672ffadb6 100644
--- a/sbroad-core/src/frontend/sql/ir/tests/limit.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests/limit.rs
@@ -48,17 +48,17 @@ vtable_max_rows = 5000
 }
 
 #[test]
-fn aggretage() {
+fn aggregate() {
     let input = r#"SELECT min("b"), min(distinct "b") FROM "t" LIMIT 1"#;
 
     let plan = sql_to_optimized_ir(input, vec![]);
 
     let expected_explain = String::from(
         r#"limit 1
-    projection (min(("min_13"::unsigned))::scalar -> "col_1", min(distinct ("column_15"::unsigned))::scalar -> "col_2")
+    projection (min(("min_096"::unsigned))::scalar -> "col_1", min(distinct ("column_864"::unsigned))::scalar -> "col_2")
         motion [policy: full]
             scan
-                projection ("t"."b"::unsigned -> "column_15", min(("t"."b"::unsigned))::scalar -> "min_13")
+                projection ("t"."b"::unsigned -> "column_864", min(("t"."b"::unsigned))::scalar -> "min_096")
                     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:
@@ -80,11 +80,11 @@ fn group_by() {
         r#"limit 555
     motion [policy: full]
         limit 555
-            projection (sum(("count_26"::integer))::decimal -> "col_1", "column_12"::unsigned -> "b")
-                group by ("column_12"::unsigned) output: ("column_12"::unsigned -> "column_12", "count_26"::integer -> "count_26")
-                    motion [policy: segment([ref("column_12")])]
+            projection (sum(("count_096"::integer))::decimal -> "col_1", "column_764"::unsigned -> "b")
+                group by ("column_764"::unsigned) output: ("column_764"::unsigned -> "column_764", "count_096"::integer -> "count_096")
+                    motion [policy: segment([ref("column_764")])]
                         scan
-                            projection ("t"."b"::unsigned -> "column_12", count((*::integer))::integer -> "count_26")
+                            projection ("t"."b"::unsigned -> "column_764", count((*::integer))::integer -> "count_096")
                                 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:
diff --git a/sbroad-core/src/frontend/sql/ir/tests/single.rs b/sbroad-core/src/frontend/sql/ir/tests/single.rs
index 798f10e8b..df31f7c17 100644
--- a/sbroad-core/src/frontend/sql/ir/tests/single.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests/single.rs
@@ -1,7 +1,7 @@
 use smol_str::{SmolStr, ToSmolStr};
 
-use crate::ir::expression::NodeId;
-use crate::ir::operator::Relational;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{Motion, NodeId};
 use crate::ir::transformation::helpers::sql_to_optimized_ir;
 use crate::ir::transformation::redistribution::{MotionKey, MotionPolicy, Target};
 use crate::ir::tree::traversal::{PostOrder, REL_CAPACITY};
@@ -35,7 +35,7 @@ impl Plan {
         let node = self.get_relation_node(node_id).unwrap();
 
         match node {
-            Relational::Motion { policy, .. } => match policy {
+            Relational::Motion(Motion { policy, .. }) => match policy {
                 MotionPolicy::None => Policy::None,
                 MotionPolicy::Full => Policy::Full,
                 MotionPolicy::Local => Policy::Local,
@@ -81,7 +81,7 @@ fn check_join_motions(
         .iter(plan.get_top().unwrap())
         .find(|level_node| -> bool {
             let rel = plan.get_relation_node(level_node.1).unwrap();
-            matches!(rel, Relational::Join { .. })
+            matches!(rel, Relational::Join(_))
         })
         .unwrap();
     let join_id = level_node.1;
diff --git a/sbroad-core/src/ir.rs b/sbroad-core/src/ir.rs
index 10732d04a..9f1ad55fa 100644
--- a/sbroad-core/src/ir.rs
+++ b/sbroad-core/src/ir.rs
@@ -1,32 +1,37 @@
-//! Intermediate representation (IR) module.
-//!
 //! Contains the logical plan tree and helpers.
 
 use base64ct::{Base64, Encoding};
+use expression::Position;
+use node::acl::{Acl, MutAcl};
+use node::block::{Block, MutBlock};
+use node::ddl::{Ddl, MutDdl};
+use node::expression::{Expression, MutExpression};
+use node::relational::{MutRelational, Relational};
+use node::{Invalid, Parameter, SizeNode};
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 use std::cell::{RefCell, RefMut};
 use std::collections::hash_map::IntoIter;
 use std::collections::{HashMap, HashSet};
 use std::fmt::{Display, Formatter};
+use std::slice::Iter;
 use tree::traversal::LevelNode;
 
-use std::slice::Iter;
 use tarantool::tlua;
 
-use acl::Acl;
-use block::Block;
-use ddl::Ddl;
-use expression::Expression;
-use operator::{Arithmetic, Relational};
+use operator::Arithmetic;
 use relation::{Table, Type};
 
 use crate::errors::Entity::Query;
 use crate::errors::{Action, Entity, SbroadError, TypeError};
 use crate::executor::engine::helpers::to_user;
 use crate::executor::engine::TableVersionMap;
-use crate::ir::expression::Expression::StableFunction;
 use crate::ir::helpers::RepeatableState;
+use crate::ir::node::{
+    Alias, ArenaType, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Constant, ExprInParentheses,
+    GroupBy, Insert, Motion, MutNode, Node, Node136, Node224, Node32, Node64, Node96, NodeId,
+    NodeOwned, Projection, Reference, Row, ScanRelation, StableFunction, Trim, UnaryExpr, Values,
+};
 use crate::ir::operator::Bool;
 use crate::ir::relation::Column;
 use crate::ir::tree::traversal::{
@@ -36,7 +41,6 @@ use crate::ir::undo::TransformationLog;
 use crate::ir::value::Value;
 use crate::{collection, error, warn};
 
-use self::expression::{NodeId, Position};
 use self::parameters::Parameters;
 use self::relation::Relations;
 use self::transformation::redistribution::MotionPolicy;
@@ -51,6 +55,7 @@ pub mod distribution;
 pub mod expression;
 pub mod function;
 pub mod helpers;
+pub mod node;
 pub mod operator;
 pub mod parameters;
 pub mod relation;
@@ -62,124 +67,388 @@ pub mod value;
 const DEFAULT_VTABLE_MAX_ROWS: u64 = 5000;
 const DEFAULT_VDBE_MAX_STEPS: u64 = 45000;
 
-/// Plan tree node.
-///
-/// There are two kinds of node variants: expressions and relational
-/// operators. Both of them can easily refer each other in the
-/// tree as they are stored in the same node arena. The reasons
-/// to separate them are:
-///
-/// - they should be treated with quite different logic
-/// - we don't want to have a single huge enum
-///
-/// Enum was chosen as we don't want to mess with dynamic
-/// dispatching and its performance penalties.
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
-pub enum Node {
-    Acl(Acl),
-    Block(Block),
-    Ddl(Ddl),
-    Expression(Expression),
-    Relational(Relational),
-    // The parameter type is inferred from the context. A typical value is None, i. e. any type.
-    // However, there is a special case where we can be more specific. According to Postgres,
-    // the parameter type can be specified by casting the parameter to a particular type.
-    // Thus, when we cast a parameter, we also assign it a type.
-    Parameter(Option<Type>),
-}
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Hash, Copy)]
-pub enum ArenaType {
-    Default,
-}
-
-impl Display for ArenaType {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
 /// Plan nodes storage.
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct Nodes {
-    /// We don't want to mess with the borrow checker and RefCell/Rc,
-    /// so all nodes are stored in the single arena ("nodes" array).
-    /// The positions in the array act like pointers, so it is possible
+    /// The positions in the arrays act like pointers, so it is possible
     /// only to add nodes to the plan, but never remove them.
-    arena: Vec<Node>,
+    arena32: Vec<Node32>,
+    arena64: Vec<Node64>,
+    arena96: Vec<Node96>,
+    arena136: Vec<Node136>,
+    arena224: Vec<Node224>,
 }
 
 impl Nodes {
-    pub(crate) fn get(&self, id: NodeId) -> Option<&Node> {
-        let offset = usize::try_from(id.offset).unwrap();
+    pub(crate) fn get(&self, id: NodeId) -> Option<Node> {
         match id.arena_type {
-            ArenaType::Default => self.arena.get(offset),
+            ArenaType::Arena32 => self.arena32.get(id.offset as usize).map(|node| match node {
+                Node32::Alias(alias) => Node::Expression(Expression::Alias(alias)),
+                Node32::Arithmetic(arithm) => Node::Expression(Expression::Arithmetic(arithm)),
+                Node32::Bool(bool) => Node::Expression(Expression::Bool(bool)),
+                Node32::Concat(concat) => Node::Expression(Expression::Concat(concat)),
+                Node32::Cast(cast) => Node::Expression(Expression::Cast(cast)),
+                Node32::CountAsterisk(count) => Node::Expression(Expression::CountAsterisk(count)),
+                Node32::Except(except) => Node::Relational(Relational::Except(except)),
+                Node32::ExprInParentheses(expr) => {
+                    Node::Expression(Expression::ExprInParentheses(expr))
+                }
+                Node32::Intersect(intersect) => Node::Relational(Relational::Intersect(intersect)),
+                Node32::Invalid(inv) => Node::Invalid(inv),
+                Node32::Limit(limit) => Node::Relational(Relational::Limit(limit)),
+                Node32::Trim(trim) => Node::Expression(Expression::Trim(trim)),
+                Node32::Unary(unary) => Node::Expression(Expression::Unary(unary)),
+                Node32::Union(un) => Node::Relational(Relational::Union(un)),
+                Node32::UnionAll(union_all) => Node::Relational(Relational::UnionAll(union_all)),
+                Node32::Values(values) => Node::Relational(Relational::Values(values)),
+            }),
+            ArenaType::Arena64 => self.arena64.get(id.offset as usize).map(|node| match node {
+                Node64::Case(case) => Node::Expression(Expression::Case(case)),
+                Node64::Constant(constant) => Node::Expression(Expression::Constant(constant)),
+                Node64::CreateRole(create_role) => Node::Acl(Acl::CreateRole(create_role)),
+                Node64::Delete(delete) => Node::Relational(Relational::Delete(delete)),
+                Node64::DropIndex(drop_index) => Node::Ddl(Ddl::DropIndex(drop_index)),
+                Node64::DropRole(drop_role) => Node::Acl(Acl::DropRole(drop_role)),
+                Node64::DropTable(drop_table) => Node::Ddl(Ddl::DropTable(drop_table)),
+                Node64::Row(row) => Node::Expression(Expression::Row(row)),
+                Node64::DropUser(drop_user) => Node::Acl(Acl::DropUser(drop_user)),
+                Node64::GroupBy(group_by) => Node::Relational(Relational::GroupBy(group_by)),
+                Node64::Having(having) => Node::Relational(Relational::Having(having)),
+                Node64::Join(join) => Node::Relational(Relational::Join(join)),
+                Node64::OrderBy(order_by) => Node::Relational(Relational::OrderBy(order_by)),
+                Node64::Parameter(param) => Node::Parameter(param),
+                Node64::Procedure(proc) => Node::Block(Block::Procedure(proc)),
+                Node64::Projection(proj) => Node::Relational(Relational::Projection(proj)),
+                Node64::Reference(reference) => Node::Expression(Expression::Reference(reference)),
+                Node64::ScanCte(scan_cte) => Node::Relational(Relational::ScanCte(scan_cte)),
+                Node64::ScanRelation(scan_rel) => {
+                    Node::Relational(Relational::ScanRelation(scan_rel))
+                }
+                Node64::ScanSubQuery(scan_squery) => {
+                    Node::Relational(Relational::ScanSubQuery(scan_squery))
+                }
+                Node64::Selection(sel) => Node::Relational(Relational::Selection(sel)),
+                Node64::SetParam(set_param) => Node::Ddl(Ddl::SetParam(set_param)),
+                Node64::SetTransaction(set_trans) => Node::Ddl(Ddl::SetTransaction(set_trans)),
+                Node64::ValuesRow(values_row) => {
+                    Node::Relational(Relational::ValuesRow(values_row))
+                }
+            }),
+            ArenaType::Arena96 => self.arena96.get(id.offset as usize).map(|node| match node {
+                Node96::DropProc(drop_proc) => Node::Ddl(Ddl::DropProc(drop_proc)),
+                Node96::Insert(insert) => Node::Relational(Relational::Insert(insert)),
+                Node96::Invalid(inv) => Node::Invalid(inv),
+                Node96::StableFunction(stable_func) => {
+                    Node::Expression(Expression::StableFunction(stable_func))
+                }
+            }),
+            ArenaType::Arena136 => self
+                .arena136
+                .get(id.offset as usize)
+                .map(|node| match node {
+                    Node136::AlterUser(alter_user) => Node::Acl(Acl::AlterUser(alter_user)),
+                    Node136::CreateProc(create_proc) => Node::Ddl(Ddl::CreateProc(create_proc)),
+                    Node136::RevokePrivilege(revoke_priv) => {
+                        Node::Acl(Acl::RevokePrivilege(revoke_priv))
+                    }
+                    Node136::GrantPrivilege(grant_priv) => {
+                        Node::Acl(Acl::GrantPrivilege(grant_priv))
+                    }
+                    Node136::Update(update) => Node::Relational(Relational::Update(update)),
+                    Node136::AlterSystem(alter_system) => Node::Ddl(Ddl::AlterSystem(alter_system)),
+                    Node136::CreateUser(create_user) => Node::Acl(Acl::CreateUser(create_user)),
+                    Node136::Invalid(inv) => Node::Invalid(inv),
+                    Node136::Motion(motion) => Node::Relational(Relational::Motion(motion)),
+                    Node136::RenameRoutine(rename_routine) => {
+                        Node::Ddl(Ddl::RenameRoutine(rename_routine))
+                    }
+                }),
+            ArenaType::Arena224 => self
+                .arena224
+                .get(id.offset as usize)
+                .map(|node| match node {
+                    Node224::CreateIndex(create_index) => Node::Ddl(Ddl::CreateIndex(create_index)),
+                    Node224::CreateTable(create_table) => Node::Ddl(Ddl::CreateTable(create_table)),
+                    Node224::Invalid(inv) => Node::Invalid(inv),
+                }),
         }
     }
 
-    pub(crate) fn get_mut(&mut self, id: NodeId) -> Option<&mut Node> {
-        let offset = usize::try_from(id.offset).unwrap();
+    #[allow(clippy::too_many_lines)]
+    pub(crate) fn get_mut(&mut self, id: NodeId) -> Option<MutNode> {
         match id.arena_type {
-            ArenaType::Default => self.arena.get_mut(offset),
+            ArenaType::Arena32 => self
+                .arena32
+                .get_mut(id.offset as usize)
+                .map(|node| match node {
+                    Node32::Alias(alias) => MutNode::Expression(MutExpression::Alias(alias)),
+                    Node32::Arithmetic(arithm) => {
+                        MutNode::Expression(MutExpression::Arithmetic(arithm))
+                    }
+                    Node32::Bool(bool) => MutNode::Expression(MutExpression::Bool(bool)),
+                    Node32::Limit(limit) => MutNode::Relational(MutRelational::Limit(limit)),
+                    Node32::Concat(concat) => MutNode::Expression(MutExpression::Concat(concat)),
+                    Node32::Cast(cast) => MutNode::Expression(MutExpression::Cast(cast)),
+                    Node32::CountAsterisk(count) => {
+                        MutNode::Expression(MutExpression::CountAsterisk(count))
+                    }
+                    Node32::Except(except) => MutNode::Relational(MutRelational::Except(except)),
+                    Node32::ExprInParentheses(expr) => {
+                        MutNode::Expression(MutExpression::ExprInParentheses(expr))
+                    }
+                    Node32::Intersect(intersect) => {
+                        MutNode::Relational(MutRelational::Intersect(intersect))
+                    }
+                    Node32::Invalid(inv) => MutNode::Invalid(inv),
+                    Node32::Trim(trim) => MutNode::Expression(MutExpression::Trim(trim)),
+                    Node32::Unary(unary) => MutNode::Expression(MutExpression::Unary(unary)),
+                    Node32::Union(un) => MutNode::Relational(MutRelational::Union(un)),
+                    Node32::UnionAll(union_all) => {
+                        MutNode::Relational(MutRelational::UnionAll(union_all))
+                    }
+                    Node32::Values(values) => MutNode::Relational(MutRelational::Values(values)),
+                }),
+            ArenaType::Arena64 => self
+                .arena64
+                .get_mut(id.offset as usize)
+                .map(|node| match node {
+                    Node64::Case(case) => MutNode::Expression(MutExpression::Case(case)),
+                    Node64::Constant(constant) => {
+                        MutNode::Expression(MutExpression::Constant(constant))
+                    }
+                    Node64::CreateRole(create_role) => {
+                        MutNode::Acl(MutAcl::CreateRole(create_role))
+                    }
+                    Node64::Delete(delete) => MutNode::Relational(MutRelational::Delete(delete)),
+                    Node64::DropIndex(drop_index) => MutNode::Ddl(MutDdl::DropIndex(drop_index)),
+                    Node64::Row(row) => MutNode::Expression(MutExpression::Row(row)),
+                    Node64::DropRole(drop_role) => MutNode::Acl(MutAcl::DropRole(drop_role)),
+                    Node64::DropTable(drop_table) => MutNode::Ddl(MutDdl::DropTable(drop_table)),
+                    Node64::DropUser(drop_user) => MutNode::Acl(MutAcl::DropUser(drop_user)),
+                    Node64::GroupBy(group_by) => {
+                        MutNode::Relational(MutRelational::GroupBy(group_by))
+                    }
+                    Node64::Having(having) => MutNode::Relational(MutRelational::Having(having)),
+                    Node64::Join(join) => MutNode::Relational(MutRelational::Join(join)),
+                    Node64::OrderBy(order_by) => {
+                        MutNode::Relational(MutRelational::OrderBy(order_by))
+                    }
+                    Node64::Parameter(param) => MutNode::Parameter(param),
+                    Node64::Procedure(proc) => MutNode::Block(MutBlock::Procedure(proc)),
+                    Node64::Projection(proj) => {
+                        MutNode::Relational(MutRelational::Projection(proj))
+                    }
+                    Node64::Reference(reference) => {
+                        MutNode::Expression(MutExpression::Reference(reference))
+                    }
+                    Node64::ScanCte(scan_cte) => {
+                        MutNode::Relational(MutRelational::ScanCte(scan_cte))
+                    }
+                    Node64::ScanRelation(scan_rel) => {
+                        MutNode::Relational(MutRelational::ScanRelation(scan_rel))
+                    }
+                    Node64::ScanSubQuery(scan_squery) => {
+                        MutNode::Relational(MutRelational::ScanSubQuery(scan_squery))
+                    }
+                    Node64::Selection(sel) => MutNode::Relational(MutRelational::Selection(sel)),
+                    Node64::SetParam(set_param) => MutNode::Ddl(MutDdl::SetParam(set_param)),
+                    Node64::SetTransaction(set_trans) => {
+                        MutNode::Ddl(MutDdl::SetTransaction(set_trans))
+                    }
+                    Node64::ValuesRow(values_row) => {
+                        MutNode::Relational(MutRelational::ValuesRow(values_row))
+                    }
+                }),
+            ArenaType::Arena96 => self
+                .arena96
+                .get_mut(id.offset as usize)
+                .map(|node| match node {
+                    Node96::DropProc(drop_proc) => MutNode::Ddl(MutDdl::DropProc(drop_proc)),
+                    Node96::Insert(insert) => MutNode::Relational(MutRelational::Insert(insert)),
+                    Node96::Invalid(inv) => MutNode::Invalid(inv),
+                    Node96::StableFunction(stable_func) => {
+                        MutNode::Expression(MutExpression::StableFunction(stable_func))
+                    }
+                }),
+            ArenaType::Arena136 => {
+                self.arena136
+                    .get_mut(id.offset as usize)
+                    .map(|node| match node {
+                        Node136::AlterUser(alter_user) => {
+                            MutNode::Acl(MutAcl::AlterUser(alter_user))
+                        }
+                        Node136::CreateProc(create_proc) => {
+                            MutNode::Ddl(MutDdl::CreateProc(create_proc))
+                        }
+                        Node136::GrantPrivilege(grant_priv) => {
+                            MutNode::Acl(MutAcl::GrantPrivilege(grant_priv))
+                        }
+                        Node136::CreateUser(create_user) => {
+                            MutNode::Acl(MutAcl::CreateUser(create_user))
+                        }
+                        Node136::RevokePrivilege(revoke_priv) => {
+                            MutNode::Acl(MutAcl::RevokePrivilege(revoke_priv))
+                        }
+                        Node136::Invalid(inv) => MutNode::Invalid(inv),
+                        Node136::AlterSystem(alter_system) => {
+                            MutNode::Ddl(MutDdl::AlterSystem(alter_system))
+                        }
+                        Node136::Update(update) => {
+                            MutNode::Relational(MutRelational::Update(update))
+                        }
+                        Node136::Motion(motion) => {
+                            MutNode::Relational(MutRelational::Motion(motion))
+                        }
+                        Node136::RenameRoutine(rename_routine) => {
+                            MutNode::Ddl(MutDdl::RenameRoutine(rename_routine))
+                        }
+                    })
+            }
+            ArenaType::Arena224 => {
+                self.arena224
+                    .get_mut(id.offset as usize)
+                    .map(|node| match node {
+                        Node224::CreateIndex(create_index) => {
+                            MutNode::Ddl(MutDdl::CreateIndex(create_index))
+                        }
+                        Node224::Invalid(inv) => MutNode::Invalid(inv),
+                        Node224::CreateTable(create_table) => {
+                            MutNode::Ddl(MutDdl::CreateTable(create_table))
+                        }
+                    })
+            }
         }
     }
 
-    /// Get the amount of relational nodes in the plan.
+    /// Returns the next node position
+    /// # Panics
     #[must_use]
-    pub fn relation_node_amount(&self) -> usize {
-        self.arena
-            .iter()
-            .filter(|node| matches!(node, Node::Relational(_)))
-            .count()
+    pub fn next_id(&self, arena_type: ArenaType) -> NodeId {
+        match arena_type {
+            ArenaType::Arena32 => NodeId {
+                offset: u32::try_from(self.arena32.len()).unwrap(),
+                arena_type: ArenaType::Arena32,
+            },
+            ArenaType::Arena64 => NodeId {
+                offset: u32::try_from(self.arena64.len()).unwrap(),
+                arena_type: ArenaType::Arena64,
+            },
+            ArenaType::Arena96 => NodeId {
+                offset: u32::try_from(self.arena96.len()).unwrap(),
+                arena_type: ArenaType::Arena96,
+            },
+            ArenaType::Arena136 => NodeId {
+                offset: u32::try_from(self.arena136.len()).unwrap(),
+                arena_type: ArenaType::Arena136,
+            },
+            ArenaType::Arena224 => NodeId {
+                offset: u32::try_from(self.arena224.len()).unwrap(),
+                arena_type: ArenaType::Arena224,
+            },
+        }
     }
 
-    /// Get the amount of expression nodes in the plan.
     #[must_use]
-    pub fn expression_node_amount(&self) -> usize {
-        self.arena
-            .iter()
-            .filter(|node| matches!(node, Node::Expression(_)))
-            .count()
+    pub fn len(&self) -> usize {
+        self.arena32.len()
+            + self.arena64.len()
+            + self.arena96.len()
+            + self.arena136.len()
+            + self.arena224.len()
     }
 
     #[must_use]
     pub fn is_empty(&self) -> bool {
-        self.arena.is_empty()
+        self.arena32.is_empty()
+            && self.arena64.is_empty()
+            && self.arena96.is_empty()
+            && self.arena136.is_empty()
+            && self.arena224.is_empty()
     }
 
-    #[must_use]
-    pub fn len(&self) -> usize {
-        self.arena.len()
+    // #[must_use]
+    // pub fn len(&self) -> usize {
+    //     self.arena.len()
+    // }
+
+    pub fn iter32(&self) -> Iter<'_, Node32> {
+        self.arena32.iter()
+    }
+
+    pub fn iter64(&self) -> Iter<'_, Node64> {
+        self.arena64.iter()
+    }
+
+    pub fn iter96(&self) -> Iter<'_, Node96> {
+        self.arena96.iter()
+    }
+
+    pub fn iter136(&self) -> Iter<'_, Node136> {
+        self.arena136.iter()
     }
 
-    pub fn iter(&self) -> Iter<'_, Node> {
-        self.arena.iter()
+    pub fn iter224(&self) -> Iter<'_, Node224> {
+        self.arena224.iter()
     }
 
     /// Add new node to arena.
+    /// # Panics
     ///
     /// # Panics
     /// Inserts a new node to the arena and returns its position,
     /// that is treated as a pointer.
-    pub fn push(&mut self, node: Node) -> NodeId {
-        let position = self.arena.len();
-        self.arena.push(node);
+    pub fn push(&mut self, node: SizeNode) -> NodeId {
+        match node {
+            SizeNode::Node32(node32) => {
+                let new_node_id = NodeId {
+                    offset: u32::try_from(self.arena32.len()).unwrap(),
+                    arena_type: ArenaType::Arena32,
+                };
 
-        NodeId {
-            offset: u32::try_from(position).unwrap(),
-            arena_type: ArenaType::Default,
-        }
-    }
+                self.arena32.push(node32);
 
-    /// # Panics
-    /// Returns the next node position
-    #[must_use]
-    pub fn next_id(&self, arena_type: ArenaType) -> NodeId {
-        match arena_type {
-            ArenaType::Default => NodeId {
-                offset: u32::try_from(self.arena.len()).unwrap(),
-                arena_type: ArenaType::Default,
-            },
+                new_node_id
+            }
+            SizeNode::Node64(node64) => {
+                let new_node_id = NodeId {
+                    offset: u32::try_from(self.arena64.len()).unwrap(),
+                    arena_type: ArenaType::Arena64,
+                };
+
+                self.arena64.push(node64);
+
+                new_node_id
+            }
+            SizeNode::Node96(node96) => {
+                let new_node_id = NodeId {
+                    offset: u32::try_from(self.arena96.len()).unwrap(),
+                    arena_type: ArenaType::Arena96,
+                };
+
+                self.arena96.push(node96);
+
+                new_node_id
+            }
+            SizeNode::Node136(node136) => {
+                let new_node_id = NodeId {
+                    offset: u32::try_from(self.arena136.len()).unwrap(),
+                    arena_type: ArenaType::Arena136,
+                };
+
+                self.arena136.push(node136);
+
+                new_node_id
+            }
+            SizeNode::Node224(node224) => {
+                let new_node_id = NodeId {
+                    offset: u32::try_from(self.arena224.len()).unwrap(),
+                    arena_type: ArenaType::Arena224,
+                };
+
+                self.arena224.push(node224);
+
+                new_node_id
+            }
         }
     }
 
@@ -187,37 +456,37 @@ impl Nodes {
     ///
     /// # Errors
     /// - The node with the given position doesn't exist.
-    pub fn replace(&mut self, id: NodeId, node: Node) -> Result<Node, SbroadError> {
+    pub fn replace(&mut self, id: NodeId, node: Node64) -> Result<Node64, SbroadError> {
+        let offset = id.offset as usize;
+
         match id.arena_type {
-            ArenaType::Default => {
-                if id.offset as usize >= self.arena.len() {
+            ArenaType::Arena64 => {
+                if offset >= self.arena64.len() {
                     return Err(SbroadError::UnexpectedNumberOfValues(format_smolstr!(
                         "can't replace node with id {id:?} as it is out of arena bounds"
                     )));
                 }
             }
+            _ => {
+                return Err(SbroadError::Invalid(
+                    Entity::Node,
+                    Some(format_smolstr!("node {:?} is invalid", node)),
+                ));
+            }
         };
 
-        let old_node = std::mem::replace(&mut self.arena[id.offset as usize], node);
+        let old_node = std::mem::replace(&mut self.arena64[offset], node);
         Ok(old_node)
     }
-
-    pub fn reserve(&mut self, capacity: usize) {
-        self.arena.reserve(capacity);
-    }
-
-    pub fn shrink_to_fit(&mut self) {
-        self.arena.shrink_to_fit();
-    }
 }
 
-impl<'nodes> IntoIterator for &'nodes Nodes {
-    type Item = &'nodes Node;
-    type IntoIter = Iter<'nodes, Node>;
-    fn into_iter(self) -> Self::IntoIter {
-        self.iter()
-    }
-}
+// impl<'nodes> IntoIterator for &'nodes Nodes {
+//     type Item = &'nodes Node;
+//     type IntoIter = Iter<'nodes, Node>;
+//     fn into_iter(self) -> Self::IntoIter {
+//         self.iter()
+//     }
+// }
 
 /// One level of `Slices`.
 /// Element of `slice` vec is a `motion_id` to execute.
@@ -543,20 +812,62 @@ impl Plan {
     /// # Errors
     /// Returns `SbroadError` when the plan tree check fails.
     pub fn check(&self) -> Result<(), SbroadError> {
-        match self.top {
+        let _ = match self.top {
             None => {
                 return Err(SbroadError::Invalid(
                     Entity::Plan,
                     Some("plan tree top is None".into()),
-                ));
+                ))
             }
             Some(top) => match top.arena_type {
-                ArenaType::Default => {
-                    if self.nodes.get(top).is_none() {
-                        return Err(SbroadError::Invalid(
+                ArenaType::Arena32 => {
+                    let top_node = self.nodes.arena32.get(top.offset as usize);
+                    match top_node {
+                        Some(_) => Ok(()),
+                        None => Err(SbroadError::Invalid(
                             Entity::Plan,
-                            Some("plan tree top index is out of bouns".into()),
-                        ));
+                            Some("plan tree top index is out of bounds".into()),
+                        )),
+                    }
+                }
+                ArenaType::Arena64 => {
+                    let top_node = self.nodes.arena64.get(top.offset as usize);
+                    match top_node {
+                        Some(_) => Ok(()),
+                        None => Err(SbroadError::Invalid(
+                            Entity::Plan,
+                            Some("plan tree top index is out of bounds".into()),
+                        )),
+                    }
+                }
+                ArenaType::Arena96 => {
+                    let top_node = self.nodes.arena96.get(top.offset as usize);
+                    match top_node {
+                        Some(_) => Ok(()),
+                        None => Err(SbroadError::Invalid(
+                            Entity::Plan,
+                            Some("plan tree top index is out of bounds".into()),
+                        )),
+                    }
+                }
+                ArenaType::Arena136 => {
+                    let top_node = self.nodes.arena136.get(top.offset as usize);
+                    match top_node {
+                        Some(_) => Ok(()),
+                        None => Err(SbroadError::Invalid(
+                            Entity::Plan,
+                            Some("plan tree top index is out of bounds".into()),
+                        )),
+                    }
+                }
+                ArenaType::Arena224 => {
+                    let top_node = self.nodes.arena224.get(top.offset as usize);
+                    match top_node {
+                        Some(_) => Ok(()),
+                        None => Err(SbroadError::Invalid(
+                            Entity::Plan,
+                            Some("plan tree top index is out of bounds".into()),
+                        )),
                     }
                 }
             },
@@ -566,11 +877,74 @@ impl Plan {
         //TODO: additional consistency checks
     }
 
+    /// # Panics
+    #[must_use]
+    pub fn replace_with_stub(&mut self, dst_id: NodeId) -> NodeOwned {
+        match dst_id.arena_type {
+            ArenaType::Arena32 => {
+                let node32 = self
+                    .nodes
+                    .arena32
+                    .get_mut(usize::try_from(dst_id.offset).unwrap())
+                    .unwrap();
+                let stub = Node32::Invalid(Invalid {});
+                let node32 = std::mem::replace(node32, stub);
+                node32.into_common_node()
+            }
+            ArenaType::Arena64 => {
+                let node64 = self
+                    .nodes
+                    .arena64
+                    .get_mut(usize::try_from(dst_id.offset).unwrap())
+                    .unwrap();
+                let stub = Node64::Parameter(Parameter { param_type: None });
+                let node64 = std::mem::replace(node64, stub);
+                node64.into_common_node()
+            }
+            ArenaType::Arena96 => {
+                let node96 = self
+                    .nodes
+                    .arena96
+                    .get_mut(usize::try_from(dst_id.offset).unwrap())
+                    .unwrap();
+                let stub = Node96::Invalid(Invalid {});
+                let node96 = std::mem::replace(node96, stub);
+                node96.into_common_node()
+            }
+            ArenaType::Arena136 => {
+                let node136 = self
+                    .nodes
+                    .arena136
+                    .get_mut(usize::try_from(dst_id.offset).unwrap())
+                    .unwrap();
+                let stub = Node136::Invalid(Invalid {});
+                let node136 = std::mem::replace(node136, stub);
+                node136.into_common_node()
+            }
+            ArenaType::Arena224 => {
+                let node224 = self
+                    .nodes
+                    .arena224
+                    .get_mut(usize::try_from(dst_id.offset).unwrap())
+                    .unwrap();
+                let stub = Node224::Invalid(Invalid {});
+                let node224 = std::mem::replace(node224, stub);
+                node224.into_common_node()
+            }
+        }
+    }
+
     /// Constructor for an empty plan structure.
     #[must_use]
     pub fn new() -> Self {
         Plan {
-            nodes: Nodes { arena: Vec::new() },
+            nodes: Nodes {
+                arena32: Vec::new(),
+                arena64: Vec::new(),
+                arena96: Vec::new(),
+                arena136: Vec::new(),
+                arena224: Vec::new(),
+            },
             relations: Relations::new(),
             slices: Slices { slices: vec![] },
             top: None,
@@ -606,9 +980,11 @@ impl Plan {
             let nodes = bfs.take_nodes();
             for level_node in nodes {
                 let id = level_node.1;
-                if let Relational::Insert { .. } = self.get_relation_node(id)? {
+                if let Relational::Insert(_) = self.get_relation_node(id)? {
                     let child_id = self.get_relational_child(id, 0)?;
-                    if let Relational::Values { children, .. } = self.get_relation_node(child_id)? {
+                    if let Relational::Values(Values { children, .. }) =
+                        self.get_relation_node(child_id)?
+                    {
                         values_count = Some(children.len());
                     }
                 }
@@ -677,7 +1053,7 @@ impl Plan {
     /// Check if the plan arena is empty.
     #[must_use]
     pub fn is_empty(&self) -> bool {
-        self.nodes.arena.is_empty()
+        self.nodes.is_empty()
     }
 
     /// Get a node by its pointer (position in the node arena).
@@ -685,15 +1061,13 @@ impl Plan {
     /// # Errors
     /// Returns `SbroadError` when the node with requested index
     /// doesn't exist.
-    pub fn get_node(&self, id: NodeId) -> Result<&Node, SbroadError> {
-        match id.arena_type {
-            ArenaType::Default => match self.nodes.arena.get(id.offset as usize) {
-                None => Err(SbroadError::NotFound(
-                    Entity::Node,
-                    format_smolstr!("from {:?} arena with index {}", id.arena_type, id.offset),
-                )),
-                Some(node) => Ok(node),
-            },
+    pub fn get_node(&self, id: NodeId) -> Result<Node, SbroadError> {
+        match self.nodes.get(id) {
+            None => Err(SbroadError::NotFound(
+                Entity::Node,
+                format_smolstr!("from {:?} with index {}", id.arena_type, id.offset),
+            )),
+            Some(node) => Ok(node),
         }
     }
 
@@ -702,19 +1076,13 @@ impl Plan {
     /// # Errors
     /// Returns `SbroadError` when the node with requested index
     /// doesn't exist.
-    pub fn get_mut_node(&mut self, id: NodeId) -> Result<&mut Node, SbroadError> {
-        match id.arena_type {
-            ArenaType::Default => match self.nodes.arena.get_mut(id.offset as usize) {
-                None => Err(SbroadError::NotFound(
-                    Entity::Node,
-                    format_smolstr!(
-                        "(mutable) from {:?} arena with index {}",
-                        id.arena_type,
-                        id.offset
-                    ),
-                )),
-                Some(node) => Ok(node),
-            },
+    pub fn get_mut_node(&mut self, id: NodeId) -> Result<MutNode, SbroadError> {
+        match self.nodes.get_mut(id) {
+            None => Err(SbroadError::NotFound(
+                Entity::Node,
+                format_smolstr!("from {:?} with index {}", id.arena_type, id.offset),
+            )),
+            Some(node) => Ok(node),
         }
     }
 
@@ -752,7 +1120,7 @@ impl Plan {
     /// - Given node is not a scan
     pub fn get_scan_relation(&self, scan_id: NodeId) -> Result<&str, SbroadError> {
         let node = self.get_relation_node(scan_id)?;
-        if let Relational::ScanRelation { relation, .. } = node {
+        if let Relational::ScanRelation(ScanRelation { relation, .. }) = node {
             return Ok(relation.as_str());
         }
         Err(SbroadError::Invalid(
@@ -799,7 +1167,7 @@ impl Plan {
         let filter = |id: NodeId| -> bool {
             matches!(
                 self.get_node(id),
-                Ok(Node::Expression(Expression::StableFunction { .. }))
+                Ok(Node::Expression(Expression::StableFunction(_)))
             )
         };
         let mut dfs = PostOrderWithFilter::with_capacity(
@@ -812,7 +1180,9 @@ impl Plan {
             if !check_top && id == expr_id {
                 continue;
             }
-            if let Node::Expression(Expression::StableFunction { name, .. }) = self.get_node(id)? {
+            if let Node::Expression(Expression::StableFunction(StableFunction { name, .. })) =
+                self.get_node(id)?
+            {
                 if Expression::is_aggregate_name(name) {
                     return Ok(true);
                 }
@@ -822,19 +1192,6 @@ impl Plan {
         Ok(false)
     }
 
-    /// Construct a plan from the YAML file.
-    ///
-    /// # Errors
-    /// Returns `SbroadError` when the YAML plan is invalid.
-    pub fn from_yaml(s: &str) -> Result<Self, SbroadError> {
-        let plan: Plan = match serde_yaml::from_str(s) {
-            Ok(p) => p,
-            Err(e) => return Err(SbroadError::Invalid(Entity::Plan, Some(e.to_smolstr()))),
-        };
-        plan.check()?;
-        Ok(plan)
-    }
-
     /// Helper function for writing tests with yaml
     ///
     /// # Errors
@@ -849,16 +1206,19 @@ impl Plan {
 
     /// Get relational node and produce a new row without aliases from its output (row with aliases).
     ///
+    /// # Panics
     /// # Errors
     /// - node is not relational
     /// - node's output is not a row of aliases
     pub fn get_row_from_rel_node(&mut self, node: NodeId) -> Result<NodeId, SbroadError> {
         let n = self.get_node(node)?;
-        if let Node::Relational(rel) = n {
-            if let Node::Expression(Expression::Row { list, .. }) = self.get_node(rel.output())? {
+        if let Node::Relational(ref rel) = n {
+            if let Node::Expression(Expression::Row(Row { list, .. })) =
+                self.get_node(rel.output())?
+            {
                 let mut cols: Vec<NodeId> = Vec::with_capacity(list.len());
                 for alias in list {
-                    if let Node::Expression(Expression::Alias { child, .. }) =
+                    if let Node::Expression(Expression::Alias(Alias { child, .. })) =
                         self.get_node(*alias)?
                     {
                         cols.push(*child);
@@ -878,11 +1238,6 @@ impl Plan {
         ))
     }
 
-    #[must_use]
-    pub fn next_id(&self, arena_type: ArenaType) -> NodeId {
-        self.nodes.next_id(arena_type)
-    }
-
     /// Add condition node to the plan.
     ///
     /// # Errors
@@ -932,11 +1287,14 @@ impl Plan {
         when_blocks: Vec<(NodeId, NodeId)>,
         else_expr: Option<NodeId>,
     ) -> NodeId {
-        self.nodes.push(Node::Expression(Expression::Case {
-            search_expr,
-            else_expr,
-            when_blocks,
-        }))
+        self.nodes.push(
+            Case {
+                search_expr,
+                when_blocks,
+                else_expr,
+            }
+            .into(),
+        )
     }
 
     /// Add bool operator node to the plan.
@@ -969,7 +1327,8 @@ impl Plan {
     /// - top node doesn't exist in the plan or is invalid.
     pub fn is_block(&self) -> Result<bool, SbroadError> {
         let top_id = self.get_top()?;
-        Ok(matches!(self.get_node(top_id)?, Node::Block(..)))
+        let top = self.get_node(top_id)?;
+        Ok(matches!(top, Node::Block(_)))
     }
 
     /// Checks that plan is a dml query on global table.
@@ -990,7 +1349,8 @@ impl Plan {
     /// - top node doesn't exist in the plan or is invalid.
     pub fn is_ddl(&self) -> Result<bool, SbroadError> {
         let top_id = self.get_top()?;
-        Ok(matches!(self.get_node(top_id)?, Node::Ddl(..)))
+        let top = self.get_node(top_id)?;
+        Ok(matches!(top, Node::Ddl(_)))
     }
 
     /// Checks that plan is ACL query.
@@ -999,7 +1359,8 @@ impl Plan {
     /// - top node doesn't exist in the plan or is invalid.
     pub fn is_acl(&self) -> Result<bool, SbroadError> {
         let top_id = self.get_top()?;
-        Ok(matches!(self.get_node(top_id)?, Node::Acl(..)))
+        let top = self.get_node(top_id)?;
+        Ok(matches!(top, Node::Acl(_)))
     }
 
     /// Set top node of plan
@@ -1016,13 +1377,14 @@ impl Plan {
     /// # Errors
     /// - node doesn't exist in the plan
     /// - node is not a relational type
-    pub fn get_relation_node(&self, node_id: NodeId) -> Result<&Relational, SbroadError> {
+    pub fn get_relation_node(&self, node_id: NodeId) -> Result<Relational, SbroadError> {
         let node = self.get_node(node_id)?;
         match node {
             Node::Relational(rel) => Ok(rel),
             Node::Expression(_)
             | Node::Parameter(..)
             | Node::Ddl(..)
+            | Node::Invalid(..)
             | Node::Acl(..)
             | Node::Block(..) => Err(SbroadError::Invalid(
                 Entity::Node,
@@ -1036,17 +1398,15 @@ impl Plan {
     /// # Errors
     /// - node doesn't exist in the plan
     /// - node is not a relational type
-    pub fn get_mut_relation_node(
-        &mut self,
-        node_id: NodeId,
-    ) -> Result<&mut Relational, SbroadError> {
+    pub fn get_mut_relation_node(&mut self, node_id: NodeId) -> Result<MutRelational, SbroadError> {
         match self.get_mut_node(node_id)? {
-            Node::Relational(rel) => Ok(rel),
-            Node::Expression(_)
-            | Node::Parameter(..)
-            | Node::Ddl(..)
-            | Node::Acl(..)
-            | Node::Block(..) => Err(SbroadError::Invalid(
+            MutNode::Relational(rel) => Ok(rel),
+            MutNode::Expression(_)
+            | MutNode::Parameter(..)
+            | MutNode::Ddl(..)
+            | MutNode::Invalid(..)
+            | MutNode::Acl(..)
+            | MutNode::Block(..) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some("Node is not relational".into()),
             )),
@@ -1058,23 +1418,23 @@ impl Plan {
     /// # Errors
     /// - node doesn't exist in the plan
     /// - node is not expression type
-    pub fn get_expression_node(&self, node_id: NodeId) -> Result<&Expression, SbroadError> {
+    pub fn get_expression_node(&self, node_id: NodeId) -> Result<Expression, SbroadError> {
         match self.get_node(node_id)? {
             Node::Expression(exp) => Ok(exp),
             Node::Parameter(..) => {
-                let node = self.constants.get(node_id);
-                if let Some(Node::Expression(exp)) = node {
-                    Ok(exp)
-                } else {
-                    Err(SbroadError::Invalid(
-                        Entity::Node,
-                        Some("parameter node does not refer to an expression".into()),
-                    ))
+                if let Some(Node64::Constant(constant)) = self.constants.get(node_id) {
+                    return Ok(Expression::Constant(constant));
                 }
+
+                Err(SbroadError::Invalid(
+                    Entity::Node,
+                    Some("parameter node does not refer to an expression".into()),
+                ))
             }
-            Node::Relational(_) | Node::Ddl(..) | Node::Acl(..) | Node::Block(..) => Err(
-                SbroadError::Invalid(Entity::Node, Some("node is not Expression type".into())),
-            ),
+            _ => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("node is not Expression type".into()),
+            )),
         }
     }
 
@@ -1086,18 +1446,19 @@ impl Plan {
     pub fn get_mut_expression_node(
         &mut self,
         node_id: NodeId,
-    ) -> Result<&mut Expression, SbroadError> {
+    ) -> Result<MutExpression, SbroadError> {
         let node = self.get_mut_node(node_id)?;
         match node {
-            Node::Expression(exp) => Ok(exp),
-            Node::Relational(_)
-            | Node::Parameter(..)
-            | Node::Ddl(..)
-            | Node::Acl(..)
-            | Node::Block(..) => Err(SbroadError::Invalid(
+            MutNode::Expression(exp) => Ok(exp),
+            MutNode::Relational(_)
+            | MutNode::Parameter(..)
+            | MutNode::Ddl(..)
+            | MutNode::Invalid(..)
+            | MutNode::Acl(..)
+            | MutNode::Block(..) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!(
-                    "node ({node_id:?}) is not expression type: {node:?}"
+                    "node ({node_id}) is not expression type: {node:?}"
                 )),
             )),
         }
@@ -1107,8 +1468,15 @@ impl Plan {
     ///
     /// # Errors
     /// - supplied id does not correspond to `Row` node
-    pub fn get_row_list(&self, row_id: NodeId) -> Result<&[NodeId], SbroadError> {
-        self.get_expression_node(row_id)?.get_row_list()
+    pub fn get_row_list(&self, row_id: NodeId) -> Result<&Vec<NodeId>, SbroadError> {
+        if let Expression::Row(Row { list, .. }) = self.get_expression_node(row_id)? {
+            return Ok(list);
+        }
+
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("node is not Row".into()),
+        ))
     }
 
     /// Helper function to get id of node under alias node,
@@ -1117,12 +1485,11 @@ impl Plan {
     /// # Errors
     /// - node is not an expression node
     pub fn get_child_under_alias(&self, child_id: NodeId) -> Result<NodeId, SbroadError> {
-        match self.get_expression_node(child_id)? {
-            Expression::Alias {
-                child: alias_child, ..
-            } => Ok(*alias_child),
-            _ => Ok(child_id),
+        if let Expression::Alias(Alias { child, .. }) = self.get_expression_node(child_id)? {
+            return Ok(*child);
         }
+
+        Ok(child_id)
     }
 
     /// Gets mut list of `Row` children ids
@@ -1130,7 +1497,14 @@ impl Plan {
     /// # Errors
     /// - supplied id does not correspond to `Row` node
     pub fn get_mut_row_list(&mut self, row_id: NodeId) -> Result<&mut Vec<NodeId>, SbroadError> {
-        self.get_mut_expression_node(row_id)?.get_mut_row_list()
+        if let MutExpression::Row(Row { list, .. }) = self.get_mut_expression_node(row_id)? {
+            return Ok(list);
+        }
+
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("node is not Row".into()),
+        ))
     }
 
     /// Replace expression that is not root of the tree (== has parent)
@@ -1154,20 +1528,20 @@ impl Plan {
         new_id: NodeId,
     ) -> Result<(), SbroadError> {
         match self.get_mut_expression_node(parent_id)? {
-            Expression::Unary { child, .. }
-            | Expression::ExprInParentheses { child }
-            | Expression::Alias { child, .. }
-            | Expression::Cast { child, .. } => {
+            MutExpression::Unary(UnaryExpr { child, .. })
+            | MutExpression::ExprInParentheses(ExprInParentheses { child })
+            | MutExpression::Alias(Alias { child, .. })
+            | MutExpression::Cast(Cast { child, .. }) => {
                 if *child == old_id {
                     *child = new_id;
                     return Ok(());
                 }
             }
-            Expression::Case {
+            MutExpression::Case(Case {
                 search_expr,
                 when_blocks,
                 else_expr,
-            } => {
+            }) => {
                 if let Some(search_expr) = search_expr {
                     if *search_expr == old_id {
                         *search_expr = new_id;
@@ -1191,9 +1565,9 @@ impl Plan {
                     }
                 }
             }
-            Expression::Bool { left, right, .. }
-            | Expression::Arithmetic { left, right, .. }
-            | Expression::Concat { left, right, .. } => {
+            MutExpression::Bool(BoolExpr { left, right, .. })
+            | MutExpression::Arithmetic(ArithmeticExpr { left, right, .. })
+            | MutExpression::Concat(Concat { left, right, .. }) => {
                 if *left == old_id {
                     *left = new_id;
                     return Ok(());
@@ -1203,9 +1577,9 @@ impl Plan {
                     return Ok(());
                 }
             }
-            Expression::Trim {
+            MutExpression::Trim(Trim {
                 pattern, target, ..
-            } => {
+            }) => {
                 if let Some(pattern_id) = pattern {
                     if *pattern_id == old_id {
                         *pattern_id = new_id;
@@ -1217,7 +1591,8 @@ impl Plan {
                     return Ok(());
                 }
             }
-            Expression::Row { list: arr, .. } | StableFunction { children: arr, .. } => {
+            MutExpression::Row(Row { list: arr, .. })
+            | MutExpression::StableFunction(StableFunction { children: arr, .. }) => {
                 for child in arr.iter_mut() {
                     if *child == old_id {
                         *child = new_id;
@@ -1225,14 +1600,14 @@ impl Plan {
                     }
                 }
             }
-            Expression::Constant { .. }
-            | Expression::Reference { .. }
-            | Expression::CountAsterisk => {}
+            MutExpression::Constant { .. }
+            | MutExpression::Reference { .. }
+            | MutExpression::CountAsterisk { .. } => {}
         }
         Err(SbroadError::FailedTo(
             Action::Replace,
             Some(Entity::Expression),
-            format_smolstr!("parent expression ({parent_id:?}) has no child with id {old_id:?}"),
+            format_smolstr!("parent expression ({parent_id}) has no child with id {old_id}"),
         ))
     }
 
@@ -1247,7 +1622,7 @@ impl Plan {
         col_idx: usize,
     ) -> Result<NodeId, SbroadError> {
         let node = self.get_relation_node(groupby_id)?;
-        if let Relational::GroupBy { gr_cols, .. } = node {
+        if let Relational::GroupBy(GroupBy { gr_cols, .. }) = node {
             let col_id = gr_cols.get(col_idx).ok_or_else(|| {
                 SbroadError::UnexpectedNumberOfValues(format_smolstr!(
                     "groupby column index out of range. Node: {node:?}"
@@ -1268,7 +1643,7 @@ impl Plan {
     /// - node is not `Projection`
     pub fn get_proj_col(&self, proj_id: NodeId, col_idx: usize) -> Result<NodeId, SbroadError> {
         let node = self.get_relation_node(proj_id)?;
-        if let Relational::Projection { output, .. } = node {
+        if let Relational::Projection(Projection { output, .. }) = node {
             let col_id = self.get_row_list(*output)?.get(col_idx).ok_or_else(|| {
                 SbroadError::UnexpectedNumberOfValues(format_smolstr!(
                     "projection column index out of range. Node: {node:?}"
@@ -1288,7 +1663,7 @@ impl Plan {
     /// - node is not `GroupBy`
     pub fn get_grouping_cols(&self, groupby_id: NodeId) -> Result<&[NodeId], SbroadError> {
         let node = self.get_relation_node(groupby_id)?;
-        if let Relational::GroupBy { gr_cols, .. } = node {
+        if let Relational::GroupBy(GroupBy { gr_cols, .. }) = node {
             return Ok(gr_cols);
         }
         Err(SbroadError::Invalid(
@@ -1307,7 +1682,7 @@ impl Plan {
         new_cols: Vec<NodeId>,
     ) -> Result<(), SbroadError> {
         let node = self.get_mut_relation_node(groupby_id)?;
-        if let Relational::GroupBy { gr_cols, .. } = node {
+        if let MutRelational::GroupBy(GroupBy { gr_cols, .. }) = node {
             *gr_cols = new_cols;
             return Ok(());
         }
@@ -1327,12 +1702,12 @@ impl Plan {
     /// # Panics
     /// - Plan is in invalid state
     pub fn get_alias_from_reference_node(&self, node: &Expression) -> Result<&str, SbroadError> {
-        let Expression::Reference {
+        let Expression::Reference(Reference {
             targets,
             position,
             parent,
             ..
-        } = node
+        }) = node
         else {
             unreachable!("get_alias_from_reference_node: Node is not of a reference type");
         };
@@ -1345,7 +1720,7 @@ impl Plan {
 
         // In a case of insert we don't inspect children output tuple
         // but rather use target relation columns.
-        if let Relational::Insert { ref relation, .. } = ref_node {
+        if let Relational::Insert(Insert { ref relation, .. }) = ref_node {
             let rel = self
                 .relations
                 .get(relation)
@@ -1380,8 +1755,22 @@ impl Plan {
             .get(*position)
             .unwrap_or_else(|| panic!("Column not found at position {position} in row list"));
 
-        let col_alias_node = self.get_expression_node(*col_alias_id)?;
-        col_alias_node.get_alias_name()
+        self.get_alias_name(*col_alias_id)
+    }
+
+    /// Gets alias node name.
+    ///
+    /// # Errors
+    /// - node isn't `Alias`
+    pub fn get_alias_name(&self, alias_id: NodeId) -> Result<&str, SbroadError> {
+        if let Expression::Alias(Alias { name, .. }) = self.get_expression_node(alias_id)? {
+            return Ok(name);
+        }
+
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("node is not Alias".into()),
+        ))
     }
 
     /// Set slices of the plan.
@@ -1395,11 +1784,12 @@ impl Plan {
         let mut dfs = PostOrder::with_capacity(|x| self.subtree_iter(x, false), self.nodes.len());
         dfs.populate_nodes(top_id);
         let nodes = dfs.take_nodes();
-        let mut plan_nodes: Vec<&Node> = Vec::with_capacity(nodes.len());
+        let mut plan_nodes: Vec<Node> = Vec::with_capacity(nodes.len());
         for level_node in nodes {
-            let id = level_node.1;
-            plan_nodes.push(self.get_node(id)?);
+            let node = self.get_node(level_node.1)?;
+            plan_nodes.push(node);
         }
+
         let bytes: Vec<u8> = bincode::serialize(&plan_nodes).map_err(|e| {
             SbroadError::FailedTo(
                 Action::Serialize,
@@ -1407,6 +1797,7 @@ impl Plan {
                 format_smolstr!("plan nodes to binary: {e:?}"),
             )
         })?;
+
         let hash = Base64::encode_string(blake3::hash(&bytes).to_hex().as_bytes()).to_smolstr();
         Ok(hash)
     }
@@ -1416,7 +1807,7 @@ impl Plan {
     fn get_param_type(&self, param_id: NodeId) -> Result<Option<Type>, SbroadError> {
         let node = self.get_node(param_id)?;
         if let Node::Parameter(ty) = node {
-            return Ok(ty.clone());
+            return Ok(ty.param_type.clone());
         }
         Err(SbroadError::Invalid(
             Entity::Node,
@@ -1426,8 +1817,8 @@ impl Plan {
 
     fn set_param_type(&mut self, param_id: NodeId, ty: &Type) -> Result<(), SbroadError> {
         let node = self.get_mut_node(param_id)?;
-        if let Node::Parameter(..) = node {
-            *node = Node::Parameter(Some(ty.clone()));
+        if let MutNode::Parameter(param) = node {
+            param.param_type = Some(ty.clone());
             Ok(())
         } else {
             Err(SbroadError::Invalid(
@@ -1545,14 +1936,14 @@ impl ShardColumnsMap {
     pub fn update_node(&mut self, node_id: NodeId, plan: &Plan) -> Result<(), SbroadError> {
         let node = plan.get_relation_node(node_id)?;
         match node {
-            Relational::ScanRelation { relation, .. } => {
+            Relational::ScanRelation(ScanRelation { relation, .. }) => {
                 let table = plan.get_relation_or_error(relation)?;
                 if let Ok(Some(pos)) = table.get_bucket_id_position() {
                     self.memo.insert(node_id, [Some(pos), None]);
                 }
                 return Ok(());
             }
-            Relational::Motion { policy, .. } => {
+            Relational::Motion(Motion { policy, .. }) => {
                 // Any motion node that moves data invalidates
                 // bucket_id column selected from that space.
                 // Even Segment policy is no help, because it only
@@ -1586,9 +1977,9 @@ impl ShardColumnsMap {
             // If there is a parameter under alias
             // and we haven't bound parameters yet,
             // we will get an error.
-            let Ok(Expression::Reference {
+            let Ok(Expression::Reference(Reference {
                 targets, position, ..
-            }) = plan.get_expression_node(ref_id)
+            })) = plan.get_expression_node(ref_id)
             else {
                 continue;
             };
@@ -1651,9 +2042,9 @@ impl ShardColumnsMap {
         plan: &Plan,
     ) -> Result<(), SbroadError> {
         let node = plan.get_relation_node(node_id)?;
-        if let Relational::Motion {
+        if let Relational::Motion(Motion {
             policy, children, ..
-        } = node
+        }) = node
         {
             if matches!(policy, MotionPolicy::Local | MotionPolicy::LocalSegment(_)) {
                 return Ok(());
diff --git a/sbroad-core/src/ir/acl.rs b/sbroad-core/src/ir/acl.rs
index 54cf1e420..3e1555afe 100644
--- a/sbroad-core/src/ir/acl.rs
+++ b/sbroad-core/src/ir/acl.rs
@@ -1,9 +1,14 @@
-use crate::ir::{Entity, Node, Plan, SbroadError};
+use crate::{
+    ir::node::{MutNode, NodeId},
+    ir::{Entity, Node, Plan, SbroadError},
+};
 use serde::{Deserialize, Serialize};
-use smol_str::{format_smolstr, SmolStr, ToSmolStr};
-use tarantool::decimal::Decimal;
+use smol_str::{format_smolstr, SmolStr};
 
-use super::{ddl::ParamDef, expression::NodeId};
+use super::{
+    ddl::ParamDef,
+    node::acl::{Acl, MutAcl},
+};
 
 ::tarantool::define_str_enum! {
     /// Revoked or granted privilege.
@@ -187,76 +192,13 @@ pub enum AlterOption {
     },
 }
 
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
-pub enum Acl {
-    DropRole {
-        name: SmolStr,
-        timeout: Decimal,
-    },
-    DropUser {
-        name: SmolStr,
-        timeout: Decimal,
-    },
-    CreateRole {
-        name: SmolStr,
-        timeout: Decimal,
-    },
-    CreateUser {
-        name: SmolStr,
-        password: SmolStr,
-        auth_method: SmolStr,
-        timeout: Decimal,
-    },
-    AlterUser {
-        name: SmolStr,
-        alter_option: AlterOption,
-        timeout: Decimal,
-    },
-    GrantPrivilege {
-        grant_type: GrantRevokeType,
-        grantee_name: SmolStr,
-        timeout: Decimal,
-    },
-    RevokePrivilege {
-        revoke_type: GrantRevokeType,
-        grantee_name: SmolStr,
-        timeout: Decimal,
-    },
-}
-
-impl Acl {
-    /// Return ACL node timeout.
-    ///
-    /// # Errors
-    /// - timeout parsing error
-    pub fn timeout(&self) -> Result<f64, SbroadError> {
-        match self {
-            Acl::DropRole { ref timeout, .. }
-            | Acl::DropUser { ref timeout, .. }
-            | Acl::CreateRole { ref timeout, .. }
-            | Acl::AlterUser { ref timeout, .. }
-            | Acl::CreateUser { ref timeout, .. }
-            | Acl::RevokePrivilege { ref timeout, .. }
-            | Acl::GrantPrivilege { ref timeout, .. } => timeout,
-        }
-        .to_smolstr()
-        .parse()
-        .map_err(|e| {
-            SbroadError::Invalid(
-                Entity::SpaceMetadata,
-                Some(format_smolstr!("timeout parsing error {e:?}")),
-            )
-        })
-    }
-}
-
 impl Plan {
     /// Get ACL node from the plan arena.
     ///
     /// # Errors
     /// - the node index is absent in arena
     /// - current node is not of ACL type
-    pub fn get_acl_node(&self, node_id: NodeId) -> Result<&Acl, SbroadError> {
+    pub fn get_acl_node(&self, node_id: NodeId) -> Result<Acl, SbroadError> {
         let node = self.get_node(node_id)?;
         match node {
             Node::Acl(acl) => Ok(acl),
@@ -272,28 +214,10 @@ impl Plan {
     /// # Errors
     /// - the node index is absent in arena
     /// - current node is not of ACL type
-    pub fn get_mut_acl_node(&mut self, node_id: NodeId) -> Result<&mut Acl, SbroadError> {
+    pub fn get_mut_acl_node(&mut self, node_id: NodeId) -> Result<MutAcl, SbroadError> {
         let node = self.get_mut_node(node_id)?;
         match node {
-            Node::Acl(acl) => Ok(acl),
-            _ => Err(SbroadError::Invalid(
-                Entity::Node,
-                Some(format_smolstr!("node is not ACL type: {node:?}")),
-            )),
-        }
-    }
-
-    /// Take ACL node from the plan arena and replace it with parameter node.
-    ///
-    /// # Errors
-    /// - current node is not of ACL type
-    pub fn take_acl_node(&mut self, node_id: NodeId) -> Result<Acl, SbroadError> {
-        // Check that node is ACL type (before making any distructive operations).
-        let _ = self.get_acl_node(node_id)?;
-        // Replace ACL with parameter node.
-        let node = std::mem::replace(self.get_mut_node(node_id)?, Node::Parameter(None));
-        match node {
-            Node::Acl(acl) => Ok(acl),
+            MutNode::Acl(acl) => Ok(acl),
             _ => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!("node is not ACL type: {node:?}")),
diff --git a/sbroad-core/src/ir/aggregates.rs b/sbroad-core/src/ir/aggregates.rs
index 7db5a343e..d06ba2240 100644
--- a/sbroad-core/src/ir/aggregates.rs
+++ b/sbroad-core/src/ir/aggregates.rs
@@ -2,15 +2,16 @@ use smol_str::{format_smolstr, ToSmolStr};
 
 use crate::errors::{Entity, SbroadError};
 use crate::ir::expression::cast::Type;
-use crate::ir::expression::Expression;
+use crate::ir::node::{NodeId, Reference, StableFunction};
 use crate::ir::operator::Arithmetic;
 use crate::ir::relation::Type as RelType;
-use crate::ir::{Node, Plan, Position};
+use crate::ir::Plan;
 use std::collections::HashMap;
 use std::fmt::{Display, Formatter};
 use std::rc::Rc;
 
-use super::expression::{ColumnPositionMap, FunctionFeature, NodeId};
+use super::expression::{ColumnPositionMap, FunctionFeature, Position};
+use super::node::expression::Expression;
 
 /// The kind of aggregate function
 ///
@@ -309,18 +310,18 @@ impl SimpleAggregate {
                                      local_kind: AggregateKind,
                                      final_func: AggregateKind|
          -> Result<(), SbroadError> {
-            let ref_node = Expression::Reference {
+            let ref_node = Reference {
                 parent: Some(parent),
                 // projection has only one child
                 targets: Some(vec![0]),
                 position,
                 col_type: fun_type.clone(),
             };
-            let ref_id = plan.nodes.push(Node::Expression(ref_node));
+            let ref_id = plan.nodes.push(ref_node.into());
             let children = match self.kind {
                 AggregateKind::AVG => vec![plan.add_cast(ref_id, Type::Double)?],
                 AggregateKind::GRCONCAT => {
-                    if let Expression::StableFunction { children, .. } =
+                    if let Expression::StableFunction(StableFunction { children, .. }) =
                         plan.get_expression_node(self.fun_id)?
                     {
                         if children.len() > 1 {
@@ -351,14 +352,14 @@ impl SimpleAggregate {
             } else {
                 None
             };
-            let final_aggr = Expression::StableFunction {
+            let final_aggr = StableFunction {
                 name: final_func.to_smolstr(),
                 children,
                 feature,
                 func_type: RelType::from(final_func),
                 is_system: true,
             };
-            let aggr_id = plan.nodes.push(Node::Expression(final_aggr));
+            let aggr_id = plan.nodes.push(final_aggr.into());
             final_aggregates.insert(local_kind, aggr_id);
             Ok(())
         };
diff --git a/sbroad-core/src/ir/api/children.rs b/sbroad-core/src/ir/api/children.rs
index 2b004eac7..3cd2f250f 100644
--- a/sbroad-core/src/ir/api/children.rs
+++ b/sbroad-core/src/ir/api/children.rs
@@ -1,8 +1,8 @@
 use std::ops::{Index, Range, RangeFrom, RangeFull};
 
-use crate::ir::expression::NodeId;
+use crate::ir::node::NodeId;
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum Children<'r> {
     None,
     Single(&'r NodeId),
@@ -223,9 +223,7 @@ impl<'r> MutChildren<'r> {
             MutChildren::Single(i) => (i, MutChildren::None),
             MutChildren::Couple(l, r) => (l, MutChildren::Single(r)),
             MutChildren::Many(i) => {
-                let Some((elem, next)) = i.split_first_mut() else {
-                    return None;
-                };
+                let (elem, next) = i.split_first_mut()?;
                 (elem, MutChildren::Many(next))
             }
         };
@@ -244,9 +242,7 @@ impl<'c> Iterator for MutChildrenIter<'c> {
     fn next(&mut self) -> Option<Self::Item> {
         let inner = std::mem::take(&mut self.inner);
         if let Some(inner) = inner {
-            let Some((elem, next)) = inner.split_first() else {
-                return None;
-            };
+            let (elem, next) = inner.split_first()?;
             self.inner = Some(next);
             Some(elem)
         } else {
diff --git a/sbroad-core/src/ir/api/constant.rs b/sbroad-core/src/ir/api/constant.rs
index 50d841a0c..4fd7c5746 100644
--- a/sbroad-core/src/ir/api/constant.rs
+++ b/sbroad-core/src/ir/api/constant.rs
@@ -1,33 +1,19 @@
 use smol_str::format_smolstr;
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{Constant, Node64, NodeId, Parameter};
 use crate::ir::value::Value;
-use crate::ir::{ArenaType, Node, Nodes, Plan};
+use crate::ir::{ArenaType, Nodes, Plan};
 
-impl Expression {
+impl Expression<'_> {
     /// Gets value from const node
     ///
     /// # Errors
     /// - node isn't constant type
     pub fn as_const_value(&self) -> Result<Value, SbroadError> {
-        if let Expression::Constant { value } = self.clone() {
-            return Ok(value);
-        }
-
-        Err(SbroadError::Invalid(
-            Entity::Node,
-            Some("node is not Const type".into()),
-        ))
-    }
-
-    /// Gets reference to value from const node
-    ///
-    /// # Errors
-    /// - node isn't constant type
-    pub fn as_const_value_ref(&self) -> Result<&Value, SbroadError> {
-        if let Expression::Constant { value } = self {
-            return Ok(value);
+        if let Expression::Constant(Constant { value }) = self {
+            return Ok(value.clone());
         }
 
         Err(SbroadError::Invalid(
@@ -39,35 +25,51 @@ impl Expression {
     /// Check whether the node is a constant expression.
     #[must_use]
     pub fn is_const(&self) -> bool {
-        matches!(self, Expression::Constant { .. })
+        matches!(self, Expression::Constant(_))
     }
 }
 
 impl Nodes {
     /// Adds constant node.
     pub fn add_const(&mut self, value: Value) -> NodeId {
-        self.push(Node::Expression(Expression::Constant { value }))
+        self.push(Constant { value }.into())
     }
 }
 
 impl Plan {
+    /// Gets reference to value from const node
+    ///
+    /// # Errors
+    /// - node isn't constant type
+    pub fn as_const_value_ref(&self, const_id: NodeId) -> Result<&Value, SbroadError> {
+        if let Expression::Constant(Constant { value }) = self.get_expression_node(const_id)? {
+            return Ok(value);
+        }
+
+        Err(SbroadError::Invalid(
+            Entity::Node,
+            Some("node is not Const type".into()),
+        ))
+    }
+
     /// Add constant value to the plan.
     pub fn add_const(&mut self, v: Value) -> NodeId {
         self.nodes.add_const(v)
     }
 
+    /// # Panics
     #[must_use]
     /// # Panics
     pub fn get_const_list(&self) -> Vec<NodeId> {
         self.nodes
-            .arena
+            .arena64
             .iter()
             .enumerate()
             .filter_map(|(id, node)| {
-                if let Node::Expression(Expression::Constant { .. }) = node {
+                if let Node64::Constant(_) = node {
                     Some(NodeId {
                         offset: u32::try_from(id).unwrap(),
-                        arena_type: ArenaType::Default,
+                        arena_type: ArenaType::Arena64,
                     })
                 } else {
                     None
@@ -82,7 +84,7 @@ impl Plan {
     /// - The parameters map is corrupted (parameters map points to invalid nodes).
     pub fn restore_constants(&mut self) -> Result<(), SbroadError> {
         for (id, const_node) in self.constants.drain() {
-            if let Node::Expression(Expression::Constant { .. }) = const_node {
+            if let Node64::Constant(_) = const_node {
             } else {
                 return Err(SbroadError::Invalid(
                     Entity::Expression,
@@ -103,7 +105,9 @@ impl Plan {
     pub fn stash_constants(&mut self) -> Result<(), SbroadError> {
         let constants = self.get_const_list();
         for const_id in constants {
-            let const_node = self.nodes.replace(const_id, Node::Parameter(None))?;
+            let const_node = self
+                .nodes
+                .replace(const_id, Node64::Parameter(Parameter { param_type: None }))?;
             self.constants.insert(const_id, const_node);
         }
         Ok(())
diff --git a/sbroad-core/src/ir/api/parameter.rs b/sbroad-core/src/ir/api/parameter.rs
index 9875e3028..2a16c6b89 100644
--- a/sbroad-core/src/ir/api/parameter.rs
+++ b/sbroad-core/src/ir/api/parameter.rs
@@ -1,7 +1,12 @@
 use crate::errors::SbroadError;
-use crate::ir::block::Block;
-use crate::ir::expression::{Expression, NodeId};
-use crate::ir::operator::Relational;
+use crate::ir::node::block::{Block, MutBlock};
+use crate::ir::node::expression::{Expression, MutExpression};
+use crate::ir::node::relational::{MutRelational, Relational};
+use crate::ir::node::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, Concat, ExprInParentheses, Having, Join, MutNode,
+    Node64, NodeId, Parameter, Procedure, Row, Selection, StableFunction, Trim, UnaryExpr,
+    ValuesRow,
+};
 use crate::ir::tree::traversal::{LevelNode, PostOrder};
 use crate::ir::value::Value;
 use crate::ir::{ArenaType, Node, OptionParamValue, Plan, ValueIdx};
@@ -205,54 +210,54 @@ impl<'binder> ParamsBinder<'binder> {
                 //       trees such as OrderBy and GroupBy, because it won't influence ordering and
                 //       grouping correspondingly. These cases are handled during parsing stage.
                 Node::Relational(rel) => match rel {
-                    Relational::Having {
+                    Relational::Having(Having {
                         filter: ref param_id,
                         ..
-                    }
-                    | Relational::Selection {
+                    })
+                    | Relational::Selection(Selection {
                         filter: ref param_id,
                         ..
-                    }
-                    | Relational::Join {
+                    })
+                    | Relational::Join(Join {
                         condition: ref param_id,
                         ..
-                    } => {
+                    }) => {
                         self.cover_param_with_row(*param_id, true, &mut param_index, &mut row_ids);
                     }
                     _ => {}
                 },
                 Node::Expression(expr) => match expr {
-                    Expression::Alias {
+                    Expression::Alias(Alias {
                         child: ref param_id,
                         ..
-                    }
-                    | Expression::ExprInParentheses {
+                    })
+                    | Expression::ExprInParentheses(ExprInParentheses {
                         child: ref param_id,
-                    }
-                    | Expression::Cast {
+                    })
+                    | Expression::Cast(Cast {
                         child: ref param_id,
                         ..
-                    }
-                    | Expression::Unary {
+                    })
+                    | Expression::Unary(UnaryExpr {
                         child: ref param_id,
                         ..
-                    } => {
+                    }) => {
                         self.cover_param_with_row(*param_id, false, &mut param_index, &mut row_ids);
                     }
-                    Expression::Bool {
+                    Expression::Bool(BoolExpr {
                         ref left,
                         ref right,
                         ..
-                    }
-                    | Expression::Arithmetic {
+                    })
+                    | Expression::Arithmetic(ArithmeticExpr {
                         ref left,
                         ref right,
                         ..
-                    }
-                    | Expression::Concat {
+                    })
+                    | Expression::Concat(Concat {
                         ref left,
                         ref right,
-                    } => {
+                    }) => {
                         for param_id in &[*left, *right] {
                             self.cover_param_with_row(
                                 *param_id,
@@ -262,11 +267,11 @@ impl<'binder> ParamsBinder<'binder> {
                             );
                         }
                     }
-                    Expression::Trim {
+                    Expression::Trim(Trim {
                         ref pattern,
                         ref target,
                         ..
-                    } => {
+                    }) => {
                         let params = match pattern {
                             Some(p) => [Some(*p), Some(*target)],
                             None => [None, Some(*target)],
@@ -280,10 +285,10 @@ impl<'binder> ParamsBinder<'binder> {
                             );
                         }
                     }
-                    Expression::Row { ref list, .. }
-                    | Expression::StableFunction {
+                    Expression::Row(Row { ref list, .. })
+                    | Expression::StableFunction(StableFunction {
                         children: ref list, ..
-                    } => {
+                    }) => {
                         for param_id in list {
                             // Parameter is already under row/function so that we don't
                             // have to cover it with `add_row` call.
@@ -295,11 +300,11 @@ impl<'binder> ParamsBinder<'binder> {
                             );
                         }
                     }
-                    Expression::Case {
+                    Expression::Case(Case {
                         ref search_expr,
                         ref when_blocks,
                         ref else_expr,
-                    } => {
+                    }) => {
                         if let Some(search_expr) = search_expr {
                             self.cover_param_with_row(
                                 *search_expr,
@@ -333,10 +338,10 @@ impl<'binder> ParamsBinder<'binder> {
                     }
                     Expression::Reference { .. }
                     | Expression::Constant { .. }
-                    | Expression::CountAsterisk => {}
+                    | Expression::CountAsterisk { .. } => {}
                 },
                 Node::Block(block) => match block {
-                    Block::Procedure { ref values, .. } => {
+                    Block::Procedure(Procedure { ref values, .. }) => {
                         for param_id in values {
                             // We don't need to wrap arguments, passed into the
                             // procedure call, into the rows.
@@ -349,7 +354,7 @@ impl<'binder> ParamsBinder<'binder> {
                         }
                     }
                 },
-                Node::Parameter(..) | Node::Ddl(..) | Node::Acl(..) => {}
+                Node::Invalid(..) | Node::Parameter(..) | Node::Ddl(..) | Node::Acl(..) => {}
             }
         }
 
@@ -382,7 +387,7 @@ impl<'binder> ParamsBinder<'binder> {
             }
         }
         for (id, new_type) in exprs_to_set_ref_type {
-            let expr = self.plan.get_mut_expression_node(id)?;
+            let mut expr = self.plan.get_mut_expression_node(id)?;
             expr.set_ref_type(new_type);
         }
 
@@ -400,7 +405,7 @@ impl<'binder> ParamsBinder<'binder> {
                 let binding_node_id = if is_row {
                     *row_ids
                         .get(param_id)
-                        .unwrap_or_else(|| panic!("Row not found at position {param_id:?}"))
+                        .unwrap_or_else(|| panic!("Row not found at position {param_id}"))
                 } else {
                     get_param_value(
                         tnt_params_style,
@@ -419,64 +424,64 @@ impl<'binder> ParamsBinder<'binder> {
         for LevelNode(_, id) in &self.nodes {
             let node = self.plan.get_mut_node(*id)?;
             match node {
-                Node::Relational(rel) => match rel {
-                    Relational::Having {
+                MutNode::Relational(rel) => match rel {
+                    MutRelational::Having(Having {
                         filter: ref mut param_id,
                         ..
-                    }
-                    | Relational::Selection {
+                    })
+                    | MutRelational::Selection(Selection {
                         filter: ref mut param_id,
                         ..
-                    }
-                    | Relational::Join {
+                    })
+                    | MutRelational::Join(Join {
                         condition: ref mut param_id,
                         ..
-                    } => {
+                    }) => {
                         bind_param(param_id, true, &mut param_index);
                     }
                     _ => {}
                 },
-                Node::Expression(expr) => match expr {
-                    Expression::Alias {
+                MutNode::Expression(expr) => match expr {
+                    MutExpression::Alias(Alias {
                         child: ref mut param_id,
                         ..
-                    }
-                    | Expression::ExprInParentheses {
+                    })
+                    | MutExpression::ExprInParentheses(ExprInParentheses {
                         child: ref mut param_id,
-                    }
-                    | Expression::Cast {
+                    })
+                    | MutExpression::Cast(Cast {
                         child: ref mut param_id,
                         ..
-                    }
-                    | Expression::Unary {
+                    })
+                    | MutExpression::Unary(UnaryExpr {
                         child: ref mut param_id,
                         ..
-                    } => {
+                    }) => {
                         bind_param(param_id, false, &mut param_index);
                     }
-                    Expression::Bool {
+                    MutExpression::Bool(BoolExpr {
                         ref mut left,
                         ref mut right,
                         ..
-                    }
-                    | Expression::Arithmetic {
+                    })
+                    | MutExpression::Arithmetic(ArithmeticExpr {
                         ref mut left,
                         ref mut right,
                         ..
-                    }
-                    | Expression::Concat {
+                    })
+                    | MutExpression::Concat(Concat {
                         ref mut left,
                         ref mut right,
-                    } => {
+                    }) => {
                         for param_id in [left, right] {
                             bind_param(param_id, true, &mut param_index);
                         }
                     }
-                    Expression::Trim {
+                    MutExpression::Trim(Trim {
                         ref mut pattern,
                         ref mut target,
                         ..
-                    } => {
+                    }) => {
                         let params = match pattern {
                             Some(p) => [Some(p), Some(target)],
                             None => [None, Some(target)],
@@ -485,20 +490,20 @@ impl<'binder> ParamsBinder<'binder> {
                             bind_param(param_id, true, &mut param_index);
                         }
                     }
-                    Expression::Row { ref mut list, .. }
-                    | Expression::StableFunction {
+                    MutExpression::Row(Row { ref mut list, .. })
+                    | MutExpression::StableFunction(StableFunction {
                         children: ref mut list,
                         ..
-                    } => {
+                    }) => {
                         for param_id in list {
                             bind_param(param_id, false, &mut param_index);
                         }
                     }
-                    Expression::Case {
+                    MutExpression::Case(Case {
                         ref mut search_expr,
                         ref mut when_blocks,
                         ref mut else_expr,
-                    } => {
+                    }) => {
                         if let Some(param_id) = search_expr {
                             bind_param(param_id, false, &mut param_index);
                         }
@@ -510,18 +515,21 @@ impl<'binder> ParamsBinder<'binder> {
                             bind_param(param_id, false, &mut param_index);
                         }
                     }
-                    Expression::Reference { .. }
-                    | Expression::Constant { .. }
-                    | Expression::CountAsterisk => {}
+                    MutExpression::Reference { .. }
+                    | MutExpression::Constant { .. }
+                    | MutExpression::CountAsterisk { .. } => {}
                 },
-                Node::Block(block) => match block {
-                    Block::Procedure { ref mut values, .. } => {
+                MutNode::Block(block) => match block {
+                    MutBlock::Procedure(Procedure { ref mut values, .. }) => {
                         for param_id in values {
                             bind_param(param_id, false, &mut param_index);
                         }
                     }
                 },
-                Node::Parameter(..) | Node::Ddl(..) | Node::Acl(..) => {}
+                MutNode::Invalid(..)
+                | MutNode::Parameter(..)
+                | MutNode::Ddl(..)
+                | MutNode::Acl(..) => {}
             }
         }
 
@@ -530,7 +538,7 @@ impl<'binder> ParamsBinder<'binder> {
 
     fn update_value_rows(&mut self) -> Result<(), SbroadError> {
         for LevelNode(_, id) in &self.nodes {
-            if let Ok(Node::Relational(Relational::ValuesRow { .. })) = self.plan.get_node(*id) {
+            if let Ok(Node::Relational(Relational::ValuesRow(_))) = self.plan.get_node(*id) {
                 self.plan.update_values_row(*id)?;
             }
         }
@@ -540,7 +548,7 @@ impl<'binder> ParamsBinder<'binder> {
 
 impl Plan {
     pub fn add_param(&mut self) -> NodeId {
-        self.nodes.push(Node::Parameter(None))
+        self.nodes.push(Parameter { param_type: None }.into())
     }
 
     /// Bind params related to `Option` clause.
@@ -579,19 +587,20 @@ impl Plan {
     }
 
     // Gather all parameter nodes from the tree to a hash set.
+    /// # Panics
     #[must_use]
     /// # Panics
     pub fn get_param_set(&self) -> AHashSet<NodeId> {
         let param_set: AHashSet<NodeId> = self
             .nodes
-            .arena
+            .arena64
             .iter()
             .enumerate()
             .filter_map(|(id, node)| {
-                if let Node::Parameter(..) = node {
+                if let Node64::Parameter(_) = node {
                     Some(NodeId {
                         offset: u32::try_from(id).unwrap(),
-                        arena_type: ArenaType::Default,
+                        arena_type: ArenaType::Arena64,
                     })
                 } else {
                     None
@@ -613,7 +622,9 @@ impl Plan {
     pub fn update_values_row(&mut self, id: NodeId) -> Result<(), SbroadError> {
         let values_row = self.get_node(id)?;
         let (output_id, data_id) =
-            if let Node::Relational(Relational::ValuesRow { output, data, .. }) = values_row {
+            if let Node::Relational(Relational::ValuesRow(ValuesRow { output, data, .. })) =
+                values_row
+            {
                 (*output, *data)
             } else {
                 panic!("Expected a values row: {values_row:?}")
@@ -627,7 +638,7 @@ impl Plan {
                 .get(pos)
                 .unwrap_or_else(|| panic!("Node not found at position {pos}"));
             let alias = self.get_mut_expression_node(*alias_id)?;
-            if let Expression::Alias { ref mut child, .. } = alias {
+            if let MutExpression::Alias(Alias { ref mut child, .. }) = alias {
                 *child = new_child_id;
             } else {
                 panic!("Expected an alias: {alias:?}")
diff --git a/sbroad-core/src/ir/block.rs b/sbroad-core/src/ir/block.rs
index 5dbd9ce97..e1d0c3d11 100644
--- a/sbroad-core/src/ir/block.rs
+++ b/sbroad-core/src/ir/block.rs
@@ -1,38 +1,18 @@
 //! IR nodes representing blocks of commands.
 
 use crate::errors::{Entity, SbroadError};
+use crate::ir::node::{MutNode, NodeId};
 use crate::ir::{Node, Plan};
-use serde::{Deserialize, Serialize};
-use smol_str::{format_smolstr, SmolStr};
+use smol_str::format_smolstr;
 
-use super::expression::NodeId;
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
-pub enum Block {
-    /// Procedure body.
-    Procedure {
-        /// The name of the procedure.
-        name: SmolStr,
-        /// Passed values to the procedure.
-        values: Vec<NodeId>,
-    },
-}
-
-impl Default for Block {
-    fn default() -> Self {
-        Block::Procedure {
-            name: SmolStr::default(),
-            values: vec![],
-        }
-    }
-}
+use super::node::block::{Block, MutBlock};
 
 impl Plan {
     /// Get a reference to a block node.
     ///
     /// # Errors
     /// - the node is not a block node.
-    pub fn get_block_node(&self, node_id: NodeId) -> Result<&Block, SbroadError> {
+    pub fn get_block_node(&self, node_id: NodeId) -> Result<Block, SbroadError> {
         let node = self.get_node(node_id)?;
         match node {
             Node::Block(block) => Ok(block),
@@ -40,10 +20,11 @@ impl Plan {
             | Node::Relational(_)
             | Node::Ddl(..)
             | Node::Acl(..)
+            | Node::Invalid(..)
             | Node::Parameter(..) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!(
-                    "node {node:?} (id {node_id:?}) is not Block type"
+                    "node {node:?} (id {node_id}) is not Block type"
                 )),
             )),
         }
@@ -53,18 +34,19 @@ impl Plan {
     ///
     /// # Errors
     /// - the node is not a block node.
-    pub fn get_mut_block_node(&mut self, node_id: NodeId) -> Result<&mut Block, SbroadError> {
+    pub fn get_mut_block_node(&mut self, node_id: NodeId) -> Result<MutBlock, SbroadError> {
         let node = self.get_mut_node(node_id)?;
         match node {
-            Node::Block(block) => Ok(block),
-            Node::Expression(_)
-            | Node::Relational(_)
-            | Node::Ddl(..)
-            | Node::Acl(..)
-            | Node::Parameter(..) => Err(SbroadError::Invalid(
+            MutNode::Block(block) => Ok(block),
+            MutNode::Expression(_)
+            | MutNode::Relational(_)
+            | MutNode::Ddl(..)
+            | MutNode::Acl(..)
+            | MutNode::Invalid(..)
+            | MutNode::Parameter(..) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!(
-                    "node {node:?} (id {node_id:?}) is not Block type"
+                    "node {node:?} (id {node_id}) is not Block type"
                 )),
             )),
         }
diff --git a/sbroad-core/src/ir/ddl.rs b/sbroad-core/src/ir/ddl.rs
index 5932af5fb..b7b78eda7 100644
--- a/sbroad-core/src/ir/ddl.rs
+++ b/sbroad-core/src/ir/ddl.rs
@@ -1,17 +1,13 @@
 use crate::ir::value::Value;
 use crate::{
     errors::{Entity, SbroadError},
+    ir::node::{MutNode, NodeId},
     ir::{relation::Type as RelationType, Node, Plan},
 };
 use serde::{Deserialize, Serialize};
-use smol_str::{format_smolstr, SmolStr, ToSmolStr};
-use tarantool::space::SpaceEngineType;
-use tarantool::{
-    decimal::Decimal,
-    index::{IndexType, RtreeIndexDistanceType},
-};
+use smol_str::{format_smolstr, SmolStr};
 
-use super::expression::NodeId;
+use super::node::ddl::{Ddl, MutDdl};
 
 #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct ColumnDef {
@@ -76,120 +72,13 @@ pub enum AlterSystemType {
     },
 }
 
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
-pub enum Ddl {
-    CreateTable {
-        name: SmolStr,
-        format: Vec<ColumnDef>,
-        primary_key: Vec<SmolStr>,
-        /// If `None`, create global table.
-        sharding_key: Option<Vec<SmolStr>>,
-        /// Vinyl is supported only for sharded tables.
-        engine_type: SpaceEngineType,
-        timeout: Decimal,
-        /// Shows which tier the sharded table belongs to.
-        /// Field has value, only if it was specified in [ON TIER] part of CREATE TABLE statement.
-        /// Field is None, if:
-        /// 1) Global table.
-        /// 2) Sharded table without [ON TIER] part. In this case picodata will use default tier.
-        tier: Option<SmolStr>,
-    },
-    DropTable {
-        name: SmolStr,
-        timeout: Decimal,
-    },
-    AlterSystem {
-        ty: AlterSystemType,
-        /// In case of None, ALTER is supposed
-        /// to be executed on all tiers.
-        tier_name: Option<SmolStr>,
-        timeout: Decimal,
-    },
-    CreateProc {
-        name: SmolStr,
-        params: Vec<ParamDef>,
-        body: SmolStr,
-        language: Language,
-        timeout: Decimal,
-    },
-    DropProc {
-        name: SmolStr,
-        params: Option<Vec<ParamDef>>,
-        timeout: Decimal,
-    },
-    RenameRoutine {
-        old_name: SmolStr,
-        new_name: SmolStr,
-        params: Option<Vec<ParamDef>>,
-        timeout: Decimal,
-    },
-    CreateIndex {
-        name: SmolStr,
-        table_name: SmolStr,
-        columns: Vec<SmolStr>,
-        unique: bool,
-        index_type: IndexType,
-        bloom_fpr: Option<Decimal>,
-        page_size: Option<u32>,
-        range_size: Option<u32>,
-        run_count_per_level: Option<u32>,
-        run_size_ratio: Option<Decimal>,
-        dimension: Option<u32>,
-        distance: Option<RtreeIndexDistanceType>,
-        hint: Option<bool>,
-        timeout: Decimal,
-    },
-    DropIndex {
-        name: SmolStr,
-        timeout: Decimal,
-    },
-    SetParam {
-        scope_type: SetParamScopeType,
-        param_value: SetParamValue,
-        timeout: Decimal,
-    },
-    // TODO: Fill with actual values.
-    SetTransaction {
-        timeout: Decimal,
-    },
-}
-
-impl Ddl {
-    /// Return DDL node timeout.
-    ///
-    /// # Errors
-    /// - timeout parsing error
-    pub fn timeout(&self) -> Result<f64, SbroadError> {
-        match self {
-            Ddl::CreateTable { ref timeout, .. }
-            | Ddl::DropTable { ref timeout, .. }
-            | Ddl::CreateIndex { ref timeout, .. }
-            | Ddl::DropIndex { ref timeout, .. }
-            | Ddl::AlterSystem { ref timeout, .. }
-            | Ddl::SetParam { ref timeout, .. }
-            | Ddl::SetTransaction { ref timeout, .. }
-            | Ddl::CreateProc { ref timeout, .. }
-            | Ddl::DropProc { ref timeout, .. }
-            | Ddl::RenameRoutine { ref timeout, .. } => timeout,
-        }
-        .to_smolstr()
-        .parse()
-        .map_err(|e| {
-            SbroadError::Invalid(
-                Entity::SpaceMetadata,
-                Some(format_smolstr!("timeout parsing error {e:?}")),
-            )
-        })
-    }
-}
-
 impl Plan {
     /// Get DDL node from the plan arena.
     ///
     /// # Errors
     /// - the node index is absent in arena
     /// - current node is not of DDL type
-    pub fn get_ddl_node(&self, node_id: NodeId) -> Result<&Ddl, SbroadError> {
+    pub fn get_ddl_node(&self, node_id: NodeId) -> Result<Ddl, SbroadError> {
         let node = self.get_node(node_id)?;
         match node {
             Node::Ddl(ddl) => Ok(ddl),
@@ -205,28 +94,10 @@ impl Plan {
     /// # Errors
     /// - the node index is absent in arena
     /// - current node is not of DDL type
-    pub fn get_mut_ddl_node(&mut self, node_id: NodeId) -> Result<&mut Ddl, SbroadError> {
+    pub fn get_mut_ddl_node(&mut self, node_id: NodeId) -> Result<MutDdl, SbroadError> {
         let node = self.get_mut_node(node_id)?;
         match node {
-            Node::Ddl(ddl) => Ok(ddl),
-            _ => Err(SbroadError::Invalid(
-                Entity::Node,
-                Some(format_smolstr!("node is not DDL type: {node:?}")),
-            )),
-        }
-    }
-
-    /// Take DDL node from the plan arena and replace it with parameter node.
-    ///
-    /// # Errors
-    /// - current node is not of DDL type
-    pub fn take_ddl_node(&mut self, node_id: NodeId) -> Result<Ddl, SbroadError> {
-        // Check that node is DDL type (before making any distructive operations).
-        let _ = self.get_ddl_node(node_id)?;
-        // Replace DDL with parameter node.
-        let node = std::mem::replace(self.get_mut_node(node_id)?, Node::Parameter(None));
-        match node {
-            Node::Ddl(ddl) => Ok(ddl),
+            MutNode::Ddl(ddl) => Ok(ddl),
             _ => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!("node is not DDL type: {node:?}")),
diff --git a/sbroad-core/src/ir/distribution.rs b/sbroad-core/src/ir/distribution.rs
index 6a20a724b..ac2168c8b 100644
--- a/sbroad-core/src/ir/distribution.rs
+++ b/sbroad-core/src/ir/distribution.rs
@@ -9,11 +9,12 @@ use serde::{Deserialize, Serialize};
 use crate::collection;
 use crate::errors::{Action, Entity, SbroadError};
 use crate::ir::helpers::RepeatableState;
+use crate::ir::node::{NodeId, Reference, Row};
 use crate::ir::transformation::redistribution::{MotionKey, Target};
 
 use super::api::children::Children;
-use super::expression::{Expression, NodeId};
-use super::operator::Relational;
+use super::node::expression::{Expression, MutExpression};
+use super::node::relational::Relational;
 use super::relation::{Column, ColumnPositions};
 use super::{Node, Plan};
 
@@ -332,9 +333,9 @@ impl ReferenceInfo {
         let mut ref_map: AHashMap<ChildColumnReference, ParentColumnPosition> = AHashMap::new();
         for (parent_column_pos, id) in ir.get_row_list(row_id)?.iter().enumerate() {
             let child_id = ir.get_child_under_alias(*id)?;
-            if let Expression::Reference {
+            if let Expression::Reference(Reference {
                 targets, position, ..
-            } = ir.get_expression_node(child_id)?
+            }) = ir.get_expression_node(child_id)?
             {
                 // As the row is located in the branch relational node, the targets should be non-empty.
                 let targets = targets.as_ref().ok_or_else(|| {
@@ -420,10 +421,7 @@ impl Plan {
     /// - invalid projection node (e.g. no children)
     /// - failed to get child distribution
     pub fn set_projection_distribution(&mut self, proj_id: NodeId) -> Result<(), SbroadError> {
-        if !matches!(
-            self.get_relation_node(proj_id)?,
-            Relational::Projection { .. }
-        ) {
+        if !matches!(self.get_relation_node(proj_id)?, Relational::Projection(_)) {
             return Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!("expected projection on id: {proj_id:?}")),
@@ -441,7 +439,7 @@ impl Plan {
         let mut only_compound_exprs = true;
         for id in self.get_row_list(output_id)? {
             let child_id = self.get_child_under_alias(*id)?;
-            if let Expression::Reference { .. } = self.get_expression_node(child_id)? {
+            if let Expression::Reference(_) = self.get_expression_node(child_id)? {
                 only_compound_exprs = false;
                 break;
             }
@@ -479,12 +477,14 @@ impl Plan {
     /// - reference has invalid targets
     #[allow(clippy::too_many_lines)]
     pub fn set_distribution(&mut self, row_id: NodeId) -> Result<(), SbroadError> {
-        let row_children = self.get_expression_node(row_id)?.get_row_list()?;
+        let row_children = self.get_row_list(row_id)?;
 
         let mut parent_node = None;
         for id in row_children {
             let child_id = self.get_child_under_alias(*id)?;
-            if let Expression::Reference { parent, .. } = self.get_expression_node(child_id)? {
+            if let Expression::Reference(Reference { parent, .. }) =
+                self.get_expression_node(child_id)?
+            {
                 parent_node = *parent;
                 break;
             }
@@ -511,9 +511,9 @@ impl Plan {
                 HashMap::with_capacity_and_hasher(row_children.len(), RandomState::new());
             for (pos, id) in row_children.iter().enumerate() {
                 let child_id = self.get_child_under_alias(*id)?;
-                if let Expression::Reference {
+                if let Expression::Reference(Reference {
                     targets, position, ..
-                } = self.get_expression_node(child_id)?
+                }) = self.get_expression_node(child_id)?
                 {
                     if targets.is_some() {
                         return Err(SbroadError::Invalid(
@@ -535,10 +535,10 @@ impl Plan {
                 })
             });
             if all_found {
-                if let Expression::Row {
+                if let MutExpression::Row(Row {
                     ref mut distribution,
                     ..
-                } = self.get_mut_expression_node(row_id)?
+                }) = self.get_mut_expression_node(row_id)?
                 {
                     let keys: HashSet<Key, RepeatableState> = collection! { new_key };
                     *distribution = Some(Distribution::Segment { keys: keys.into() });
@@ -557,10 +557,10 @@ impl Plan {
                     let suggested_dist =
                         self.dist_from_child(child_id, &ref_info.child_column_to_parent_col)?;
                     let output = self.get_mut_expression_node(row_id)?;
-                    if let Expression::Row {
+                    if let MutExpression::Row(Row {
                         ref mut distribution,
                         ..
-                    } = output
+                    }) = output
                     {
                         if distribution.is_none() {
                             *distribution = Some(suggested_dist);
@@ -614,7 +614,7 @@ impl Plan {
 
         if !matches!(
             node,
-            Relational::Join { .. } | Relational::Having { .. } | Relational::Selection { .. }
+            Relational::Join(_) | Relational::Having(_) | Relational::Selection(_)
         ) {
             return Ok(None);
         }
@@ -664,10 +664,10 @@ impl Plan {
     ) -> Result<Distribution, SbroadError> {
         if let Node::Relational(relational_op) = self.get_node(child_rel_node)? {
             let node = self.get_node(relational_op.output())?;
-            if let Node::Expression(Expression::Row {
+            if let Node::Expression(Expression::Row(Row {
                 distribution: child_dist,
                 ..
-            }) = node
+            })) = node
             {
                 match child_dist {
                     None => {
@@ -725,10 +725,10 @@ impl Plan {
     /// # Errors
     /// - supplied node is `Row`
     pub fn set_dist(&mut self, row_id: NodeId, dist: Distribution) -> Result<(), SbroadError> {
-        if let Expression::Row {
+        if let MutExpression::Row(Row {
             ref mut distribution,
             ..
-        } = self.get_mut_expression_node(row_id)?
+        }) = self.get_mut_expression_node(row_id)?
         {
             *distribution = Some(dist);
             return Ok(());
@@ -765,10 +765,10 @@ impl Plan {
             }
         };
         let expr = self.get_mut_expression_node(row_id)?;
-        if let Expression::Row {
+        if let MutExpression::Row(Row {
             ref mut distribution,
             ..
-        } = expr
+        }) = expr
         {
             *distribution = Some(new_dist);
         } else {
@@ -781,13 +781,31 @@ impl Plan {
         Ok(())
     }
 
+    /// Gets current row distribution.
+    ///
+    /// # Errors
+    /// Returns `SbroadError` when the function is called on expression
+    /// other than `Row` or a node doesn't know its distribution yet.
+    pub fn distribution(&self, id: NodeId) -> Result<&Distribution, SbroadError> {
+        if let Expression::Row(Row { distribution, .. }) = self.get_expression_node(id)? {
+            let Some(dist) = distribution else {
+                return Err(SbroadError::Invalid(
+                    Entity::Distribution,
+                    Some("distribution is uninitialized".into()),
+                ));
+            };
+            return Ok(dist);
+        }
+        Err(SbroadError::Invalid(Entity::Expression, None))
+    }
+
     /// Gets distribution of the output row.
     ///
     /// # Errors
     /// - Node is not of a row type.
     pub fn get_distribution(&self, row_id: NodeId) -> Result<&Distribution, SbroadError> {
         match self.get_node(row_id)? {
-            Node::Expression(expr) => expr.distribution(),
+            Node::Expression(_) => self.distribution(row_id),
             Node::Relational(_) => Err(SbroadError::Invalid(
                 Entity::Distribution,
                 Some(
@@ -811,6 +829,10 @@ impl Plan {
                 Entity::Distribution,
                 Some("Failed to get distribution for a code block node.".to_smolstr()),
             )),
+            Node::Invalid(_) => Err(SbroadError::Invalid(
+                Entity::Distribution,
+                Some("Failed to get distribution for an invalid node.".to_smolstr()),
+            )),
         }
     }
 
diff --git a/sbroad-core/src/ir/distribution/tests.rs b/sbroad-core/src/ir/distribution/tests.rs
index 342a394e7..6b63c09a7 100644
--- a/sbroad-core/src/ir/distribution/tests.rs
+++ b/sbroad-core/src/ir/distribution/tests.rs
@@ -31,31 +31,27 @@ fn proj_preserve_dist_key() {
 
     plan.top = Some(proj_id);
 
-    let scan_output: NodeId = if let Node::Relational(scan) = plan.get_node(scan_id).unwrap() {
-        scan.output()
-    } else {
-        panic!("Invalid plan!");
-    };
+    let rel_node = plan.get_relation_node(scan_id).unwrap();
+    let scan_output = rel_node.output();
+
     plan.set_distribution(scan_output).unwrap();
-    if let Node::Expression(scan_row) = plan.get_node(scan_output).unwrap() {
-        let keys: HashSet<_, RepeatableState> = collection! { Key::new(vec![1, 0]) };
-        assert_eq!(
-            &Distribution::Segment { keys: keys.into() },
-            scan_row.distribution().unwrap()
-        );
-    }
+    let expr_node = plan.get_expression_node(scan_output).unwrap();
+    let keys: HashSet<_, RepeatableState> = collection! { Key::new(vec![1, 0]) };
+    assert_eq!(
+        &Distribution::Segment { keys: keys.into() },
+        expr_node.distribution().unwrap()
+    );
+
+    let rel_node = plan.get_relation_node(proj_id).unwrap();
+    let proj_output: NodeId = rel_node.output();
 
-    let proj_output: NodeId = if let Node::Relational(proj) = plan.get_node(proj_id).unwrap() {
-        proj.output()
-    } else {
-        panic!("Invalid plan!");
-    };
     plan.set_distribution(proj_output).unwrap();
-    if let Node::Expression(proj_row) = plan.get_node(proj_output).unwrap() {
+    let expr_node = plan.get_node(proj_output).unwrap();
+    if let Node::Expression(expr) = expr_node {
         let keys: HashSet<_, RepeatableState> = collection! { Key::new(vec![1, 0]) };
         assert_eq!(
             &Distribution::Segment { keys: keys.into() },
-            proj_row.distribution().unwrap()
+            expr.distribution().unwrap()
         );
     }
 }
@@ -68,10 +64,10 @@ fn projection_any_dist_for_expr() {
 
     // check explain first
     let expected_explain = SmolStr::from(
-        r#"projection (sum(("count_13"::integer))::decimal -> "col_1")
+        r#"projection (sum(("count_096"::integer))::decimal -> "col_1")
     motion [policy: full]
         scan
-            projection (count(("test_space"."id"::unsigned))::integer -> "count_13")
+            projection (count(("test_space"."id"::unsigned))::integer -> "count_096")
                 scan "test_space"
 execution options:
 sql_vdbe_max_steps = 45000
@@ -88,7 +84,7 @@ vtable_max_rows = 5000
             .find(|level_node| {
                 matches!(
                     plan.get_relation_node(level_node.1).unwrap(),
-                    Relational::Projection { .. }
+                    Relational::Projection(_)
                 )
             })
             .unwrap()
diff --git a/sbroad-core/src/ir/explain.rs b/sbroad-core/src/ir/explain.rs
index 60c010450..9e94e18c1 100644
--- a/sbroad-core/src/ir/explain.rs
+++ b/sbroad-core/src/ir/explain.rs
@@ -9,19 +9,26 @@ use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::to_user;
 use crate::ir::expression::cast::Type as CastType;
-use crate::ir::expression::{Expression, TrimKind};
-use crate::ir::operator::{
-    ConflictStrategy, JoinKind, OrderByElement, OrderByEntity, OrderByType, Relational,
+use crate::ir::expression::TrimKind;
+use crate::ir::node::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, Constant, Delete, GroupBy as GroupByRel, Having,
+    Insert, Join, Motion as MotionRel, NodeId, OrderBy as OrderByRel, Projection as ProjectionRel,
+    Reference, Row as RowExpr, ScanCte, ScanRelation, ScanSubQuery, Selection, StableFunction,
+    Trim, UnaryExpr, Update as UpdateRel, Values, ValuesRow,
 };
+use crate::ir::operator::{ConflictStrategy, JoinKind, OrderByElement, OrderByEntity, OrderByType};
 use crate::ir::relation::Type;
 use crate::ir::transformation::redistribution::{
     MotionKey as IrMotionKey, MotionPolicy as IrMotionPolicy, Target as IrTarget,
 };
 use crate::ir::{OptionKind, Plan};
 
-use super::expression::{FunctionFeature, NodeId};
+use super::expression::FunctionFeature;
+use super::node::expression::Expression;
+use super::node::relational::Relational;
+use super::node::Limit;
 use super::operator::{Arithmetic, Bool, Unary};
-use super::tree::traversal::{PostOrder, EXPR_CAPACITY, REL_CAPACITY};
+use super::tree::traversal::{LevelNode, PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use super::value::Value;
 
 #[derive(Debug, PartialEq, Serialize)]
@@ -130,12 +137,11 @@ impl ColExpr {
         let mut dft_post =
             PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, false), EXPR_CAPACITY);
 
-        for level_node in dft_post.iter(subtree_top) {
-            let id = level_node.1;
+        for LevelNode(_, id) in dft_post.iter(subtree_top) {
             let current_node = plan.get_expression_node(id)?;
 
             match &current_node {
-                Expression::Cast { to, .. } => {
+                Expression::Cast(Cast { to, .. }) => {
                     let (expr, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing CAST expression".to_smolstr(),
@@ -144,11 +150,11 @@ impl ColExpr {
                     let cast_expr = ColExpr::Cast(Box::new(expr), *to);
                     stack.push((cast_expr, id));
                 }
-                Expression::Case {
+                Expression::Case(Case {
                     search_expr,
                     when_blocks,
                     else_expr,
-                } => {
+                }) => {
                     let else_expr_col = if else_expr.is_some() {
                         let (expr, _) = stack
                             .pop()
@@ -185,25 +191,24 @@ impl ColExpr {
                     let cast_expr = ColExpr::Case(search_expr_col, match_expr_cols, else_expr_col);
                     stack.push((cast_expr, id));
                 }
-                Expression::CountAsterisk => {
+                Expression::CountAsterisk(_) => {
                     let count_asterisk_expr =
                         ColExpr::Column("*".to_string(), current_node.calculate_type(plan)?);
                     stack.push((count_asterisk_expr, id));
                 }
-                Expression::Reference { position, .. } => {
+                Expression::Reference(Reference { position, .. }) => {
                     let mut col_name = String::new();
 
-                    let rel_id = *plan.get_relational_from_reference_node(id)?;
-                    let rel_node = plan.get_relation_node(rel_id)?;
+                    let rel_id: NodeId = *plan.get_relational_from_reference_node(id)?;
 
-                    if let Some(name) = rel_node.scan_name(plan, *position)? {
+                    if let Some(name) = plan.scan_name(rel_id, *position)? {
                         col_name.push('"');
                         col_name.push_str(name);
                         col_name.push('"');
                         col_name.push('.');
                     }
 
-                    let alias = plan.get_alias_from_reference_node(current_node)?;
+                    let alias = plan.get_alias_from_reference_node(&current_node)?;
                     col_name.push('"');
                     col_name.push_str(alias);
                     col_name.push('"');
@@ -211,7 +216,7 @@ impl ColExpr {
                     let ref_expr = ColExpr::Column(col_name, current_node.calculate_type(plan)?);
                     stack.push((ref_expr, id));
                 }
-                Expression::Concat { .. } => {
+                Expression::Concat(_) => {
                     let (right, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing CONCAT expression".to_smolstr(),
@@ -225,12 +230,12 @@ impl ColExpr {
                     let concat_expr = ColExpr::Concat(Box::new(left), Box::new(right));
                     stack.push((concat_expr, id));
                 }
-                Expression::Constant { value } => {
+                Expression::Constant(Constant { value }) => {
                     let expr =
                         ColExpr::Column(value.to_string(), current_node.calculate_type(plan)?);
                     stack.push((expr, id));
                 }
-                Expression::Trim { kind, .. } => {
+                Expression::Trim(Trim { kind, .. }) => {
                     let (target, _) = stack
                         .pop()
                         .expect("stack is empty while processing TRIM expression");
@@ -238,14 +243,14 @@ impl ColExpr {
                     let trim_expr = ColExpr::Trim(kind.clone(), pattern, Box::new(target));
                     stack.push((trim_expr, id));
                 }
-                Expression::StableFunction {
+                Expression::StableFunction(StableFunction {
                     name,
                     children,
                     feature,
                     func_type,
                     is_system: is_aggr,
                     ..
-                } => {
+                }) => {
                     let mut len = children.len();
                     let mut args: Vec<ColExpr> = Vec::with_capacity(len);
                     while len > 0 {
@@ -267,7 +272,7 @@ impl ColExpr {
                     );
                     stack.push((func_expr, id));
                 }
-                Expression::Row { list, .. } => {
+                Expression::Row(RowExpr { list, .. }) => {
                     let mut len = list.len();
                     let mut row: Vec<(ColExpr, NodeId)> = Vec::with_capacity(len);
                     while len > 0 {
@@ -284,11 +289,11 @@ impl ColExpr {
                     let row_expr = ColExpr::Row(row);
                     stack.push((row_expr, id));
                 }
-                Expression::Arithmetic {
+                Expression::Arithmetic(ArithmeticExpr {
                     left: _,
                     op,
                     right: _,
-                } => {
+                }) => {
                     let (right, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing ARITHMETIC expression".to_smolstr(),
@@ -314,7 +319,7 @@ impl ColExpr {
                     let parentheses_expr = ColExpr::Parentheses(Box::new(child_expr));
                     stack.push((parentheses_expr, id));
                 }
-                Expression::Alias { name, .. } => {
+                Expression::Alias(Alias { name, .. }) => {
                     let (expr, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing ALIAS expression".to_smolstr(),
@@ -323,7 +328,7 @@ impl ColExpr {
                     let alias_expr = ColExpr::Alias(Box::new(expr), name.clone());
                     stack.push((alias_expr, id));
                 }
-                Expression::Bool { op, .. } => {
+                Expression::Bool(BoolExpr { op, .. }) => {
                     let (right, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing BOOL expression".to_smolstr(),
@@ -340,7 +345,7 @@ impl ColExpr {
 
                     stack.push((bool_expr, id));
                 }
-                Expression::Unary { op, .. } => {
+                Expression::Unary(UnaryExpr { op, .. }) => {
                     let (expr, _) = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "stack is empty while processing UNARY expression".to_smolstr(),
@@ -549,12 +554,12 @@ struct Update {
 impl Update {
     #[allow(dead_code)]
     fn new(plan: &Plan, update_id: NodeId) -> Result<Self, SbroadError> {
-        if let Relational::Update {
+        if let Relational::Update(UpdateRel {
             relation: ref rel,
             update_columns_map,
             output: ref output_id,
             ..
-        } = plan.get_relation_node(update_id)?
+        }) = plan.get_relation_node(update_id)?
         {
             let mut update_statements: Vec<(SmolStr, SmolStr)> =
                 Vec::with_capacity(update_columns_map.len());
@@ -588,7 +593,7 @@ impl Update {
                         )
                     })?;
                     let node = plan.get_expression_node(alias_id)?;
-                    if let Expression::Alias { name, .. } = node {
+                    if let Expression::Alias(Alias { name, .. }) = node {
                         to_user(name)
                     } else {
                         return Err(SbroadError::Invalid(
@@ -610,7 +615,7 @@ impl Update {
         Err(SbroadError::Invalid(
             Entity::Node,
             Some(format_smolstr!(
-                "explain: expected Update node on id: {update_id:?}"
+                "explain: expected Update node on id: {update_id}"
             )),
         ))
     }
@@ -712,7 +717,6 @@ impl Row {
     fn add_col(&mut self, row: RowVal) {
         self.cols.push(row);
     }
-
     fn from_col_exprs_with_ids(
         plan: &Plan,
         exprs_with_ids: &mut Vec<(ColExpr, NodeId)>,
@@ -725,7 +729,7 @@ impl Row {
 
             match &current_node {
                 Expression::Reference { .. } => {
-                    let rel_id = *plan.get_relational_from_reference_node(expr_id)?;
+                    let rel_id: NodeId = *plan.get_relational_from_reference_node(expr_id)?;
 
                     let rel_node = plan.get_relation_node(rel_id)?;
                     if plan.is_additional_child(rel_id)? {
@@ -735,7 +739,7 @@ impl Row {
                             let sq_offset = sq_ref_map.get(&rel_id).ok_or_else(|| {
                                 SbroadError::NotFound(
                                     Entity::SubQuery,
-                                    format_smolstr!("with index {rel_id:?} in the map"),
+                                    format_smolstr!("with index {rel_id} in the map"),
                                 )
                             })?;
                             row.add_col(RowVal::SqRef(Ref::new(*sq_offset)));
@@ -743,7 +747,7 @@ impl Row {
                             return Err(SbroadError::Invalid(
                                 Entity::Plan,
                                 Some(format_smolstr!(
-                                    "additional child ({rel_id:?}) is not SQ or Motion: {rel_node:?}"
+                                    "additional child ({rel_id}) is not SQ or Motion: {rel_node:?}"
                                 )),
                             ));
                         }
@@ -1032,7 +1036,7 @@ impl FullExplain {
     #[allow(dead_code)]
     #[allow(clippy::too_many_lines)]
     pub fn new(ir: &Plan, top_id: NodeId) -> Result<Self, SbroadError> {
-        let mut stack: Vec<ExplainTreePart> = Vec::with_capacity(ir.nodes.relation_node_amount());
+        let mut stack: Vec<ExplainTreePart> = Vec::new();
         let mut result = FullExplain::default();
         result
             .exec_options
@@ -1043,9 +1047,7 @@ impl FullExplain {
         ));
 
         let mut dft_post = PostOrder::with_capacity(|node| ir.nodes.rel_iter(node), REL_CAPACITY);
-        for level_node in dft_post.iter(top_id) {
-            let level = level_node.0;
-            let id = level_node.1;
+        for LevelNode(level, id) in dft_post.iter(top_id) {
             let mut current_node = ExplainTreePart::with_level(level);
             let node = ir.get_relation_node(id)?;
             current_node.current = match &node {
@@ -1071,9 +1073,9 @@ impl FullExplain {
                     }
                     Some(ExplainNode::Except)
                 }
-                Relational::GroupBy {
+                Relational::GroupBy(GroupByRel {
                     gr_cols, output, ..
-                } => {
+                }) => {
                     let child = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "Groupby node must have at least one child".into(),
@@ -1083,9 +1085,9 @@ impl FullExplain {
                     let p = GroupBy::new(ir, gr_cols, *output, &HashMap::new())?;
                     Some(ExplainNode::GroupBy(p))
                 }
-                Relational::OrderBy {
+                Relational::OrderBy(OrderByRel {
                     order_by_elements, ..
-                } => {
+                }) => {
                     let child = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "OrderBy node must have at least one child".into(),
@@ -1095,7 +1097,7 @@ impl FullExplain {
                     let o_b = OrderBy::new(ir, order_by_elements, &HashMap::new())?;
                     Some(ExplainNode::OrderBy(o_b))
                 }
-                Relational::Projection { output, .. } => {
+                Relational::Projection(ProjectionRel { output, .. }) => {
                     // TODO: change this logic when we'll enable sub-queries in projection
                     let child = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
@@ -1106,16 +1108,16 @@ impl FullExplain {
                     let p = Projection::new(ir, *output, &HashMap::new())?;
                     Some(ExplainNode::Projection(p))
                 }
-                Relational::ScanRelation {
+                Relational::ScanRelation(ScanRelation {
                     relation, alias, ..
-                } => {
+                }) => {
                     let s = Scan::new(
                         relation.to_smolstr(),
                         alias.as_ref().map(ToSmolStr::to_smolstr),
                     );
                     Some(ExplainNode::Scan(s))
                 }
-                Relational::ScanCte { alias, .. } => {
+                Relational::ScanCte(ScanCte { alias, .. }) => {
                     let child = stack.pop().expect("CTE node must have exactly one child");
                     let existing_pos = result.subqueries.iter().position(|sq| *sq == child);
                     let pos = existing_pos.unwrap_or_else(|| {
@@ -1124,12 +1126,12 @@ impl FullExplain {
                     });
                     Some(ExplainNode::Cte(alias.clone(), Ref::new(pos)))
                 }
-                Relational::Selection {
+                Relational::Selection(Selection {
                     children, filter, ..
-                }
-                | Relational::Having {
+                })
+                | Relational::Having(Having {
                     children, filter, ..
-                } => {
+                }) => {
                     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() {
@@ -1177,7 +1179,7 @@ impl FullExplain {
                         Some(ExplainNode::UnionAll)
                     }
                 }
-                Relational::ScanSubQuery { alias, .. } => {
+                Relational::ScanSubQuery(ScanSubQuery { alias, .. }) => {
                     let child = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "ScanSubQuery node must have exactly one child".into(),
@@ -1187,9 +1189,9 @@ impl FullExplain {
                     let s = SubQuery::new(alias.as_ref().map(ToString::to_string));
                     Some(ExplainNode::SubQuery(s))
                 }
-                Relational::Motion {
+                Relational::Motion(MotionRel {
                     children, policy, ..
-                } => {
+                }) => {
                     let child = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "Motion node must have exactly one child".into(),
@@ -1205,7 +1207,8 @@ impl FullExplain {
                         })?;
 
                         let child_output_id = ir.get_relation_node(*child_id)?.output();
-                        let col_list = ir.get_expression_node(child_output_id)?.get_row_list()?;
+                        let child_node = ir.get_expression_node(child_output_id)?;
+                        let col_list = child_node.get_row_list()?;
 
                         let targets = (s.targets)
                             .iter()
@@ -1247,12 +1250,12 @@ impl FullExplain {
 
                     Some(ExplainNode::Motion(m))
                 }
-                Relational::Join {
+                Relational::Join(Join {
                     children,
                     condition,
                     kind,
                     ..
-                } => {
+                }) => {
                     if children.len() < 2 {
                         return Err(SbroadError::UnexpectedNumberOfValues(
                             "Join must have at least two children".into(),
@@ -1287,7 +1290,7 @@ impl FullExplain {
                         kind: kind.clone(),
                     }))
                 }
-                Relational::ValuesRow { data, children, .. } => {
+                Relational::ValuesRow(ValuesRow { data, children, .. }) => {
                     let mut sq_ref_map: SubQueryRefMap = HashMap::with_capacity(children.len());
 
                     for sq_id in children.iter().rev() {
@@ -1306,7 +1309,7 @@ impl FullExplain {
 
                     Some(ExplainNode::ValueRow(row))
                 }
-                Relational::Values { children, .. } => {
+                Relational::Values(Values { children, .. }) => {
                     let mut amount_values = children.len();
 
                     while amount_values > 0 {
@@ -1321,11 +1324,11 @@ impl FullExplain {
                     }
                     Some(ExplainNode::Value)
                 }
-                Relational::Insert {
+                Relational::Insert(Insert {
                     relation,
                     conflict_strategy,
                     ..
-                } => {
+                }) => {
                     let values = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "Insert node failed to pop a value row.".into(),
@@ -1350,7 +1353,7 @@ impl FullExplain {
 
                     Some(ExplainNode::Update(Update::new(ir, id)?))
                 }
-                Relational::Delete { relation, .. } => {
+                Relational::Delete(Delete { relation, .. }) => {
                     let values = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "Delete node failed to pop a value row.".into(),
@@ -1361,7 +1364,7 @@ impl FullExplain {
 
                     Some(ExplainNode::Delete(relation.to_smolstr()))
                 }
-                Relational::Limit { limit, .. } => {
+                Relational::Limit(Limit { limit, .. }) => {
                     let child = stack.pop().ok_or_else(|| {
                         SbroadError::UnexpectedNumberOfValues(
                             "Limit node must have exactly one child".into(),
diff --git a/sbroad-core/src/ir/expression.rs b/sbroad-core/src/ir/expression.rs
index df68c1940..14ef78224 100644
--- a/sbroad-core/src/ir/expression.rs
+++ b/sbroad-core/src/ir/expression.rs
@@ -7,24 +7,24 @@
 //! - distribution of the data in the tuple
 
 use ahash::RandomState;
+use distribution::Distribution;
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr};
 use std::collections::{BTreeMap, HashSet};
-use std::fmt::{Display, Formatter};
 use std::hash::{Hash, Hasher};
 use std::ops::Bound::Included;
 
+use super::{
+    distribution, operator, Alias, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Constant,
+    ExprInParentheses, Expression, LevelNode, MutExpression, MutNode, Node, NodeId, Reference,
+    Relational, Row, StableFunction, Trim, UnaryExpr, Value,
+};
 use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::to_user;
-use crate::ir::aggregates::AggregateKind;
-use crate::ir::operator::{Bool, Relational};
+use crate::ir::operator::Bool;
 use crate::ir::relation::Type;
-use crate::ir::Positions as Targets;
-
-use super::distribution::Distribution;
-use super::tree::traversal::{PostOrderWithFilter, EXPR_CAPACITY};
-use super::value::Value;
-use super::{operator, ArenaType, Node, Nodes, Plan};
+use crate::ir::tree::traversal::{PostOrderWithFilter, EXPR_CAPACITY};
+use crate::ir::{Nodes, Plan, Positions as Targets};
 
 pub mod cast;
 pub mod concat;
@@ -32,181 +32,6 @@ pub mod types;
 
 pub(crate) type ExpressionId = NodeId;
 
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash, Copy)]
-pub struct NodeId {
-    pub offset: u32,
-    pub arena_type: ArenaType,
-}
-
-impl Display for NodeId {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}{}", self.offset, self.arena_type)
-    }
-}
-
-impl Default for NodeId {
-    fn default() -> Self {
-        NodeId {
-            offset: 0,
-            arena_type: ArenaType::Default,
-        }
-    }
-}
-
-/// Tuple tree build blocks.
-///
-/// A tuple describes a single portion of data moved among cluster nodes.
-/// It consists of the ordered, strictly typed expressions with names
-/// (columns) and additional information about data distribution policy.
-///
-/// Tuple is a tree with a `Row` top (level 0) and a list of the named
-/// `Alias` columns (level 1). This convention is used across the code
-/// and should not be changed. It ensures that we always know the
-/// name of any column in the tuple and therefore simplifies AST
-/// deserialization.
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
-pub enum Expression {
-    /// Expression name.
-    ///
-    /// Example: `42 as a`.
-    Alias {
-        /// Alias name.
-        name: SmolStr,
-        /// Child expression node index in the plan node arena.
-        child: NodeId,
-    },
-    /// Binary expression returning boolean result.
-    ///
-    /// Example: `a > 42`, `b in (select c from ...)`.
-    Bool {
-        /// Left branch expression node index in the plan node arena.
-        left: NodeId,
-        /// Boolean operator.
-        op: operator::Bool,
-        /// Right branch expression node index in the plan node arena.
-        right: NodeId,
-    },
-    /// Binary expression returning row result.
-    ///
-    /// Example: `a + b > 42`, `a + b < c + 1`, `1 + 2 != 2 * 2`.
-    ///
-    /// TODO: always cover children with parentheses (in to_sql).
-    Arithmetic {
-        /// Left branch expression node index in the plan node arena.
-        left: NodeId,
-        /// Arithmetic operator.
-        op: operator::Arithmetic,
-        /// Right branch expression node index in the plan node arena.
-        right: NodeId,
-    },
-    /// Type cast expression.
-    ///
-    /// Example: `cast(a as text)`.
-    Cast {
-        /// Target expression that must be casted to another type.
-        child: NodeId,
-        /// Cast type.
-        to: cast::Type,
-    },
-    /// String concatenation expression.
-    ///
-    /// Example: `a || 'hello'`.
-    Concat {
-        /// Left expression node id.
-        left: NodeId,
-        /// Right expression node id.
-        right: NodeId,
-    },
-    /// Constant expressions.
-    ///
-    /// Example: `42`.
-    Constant {
-        /// Contained value (boolean, number, string or null)
-        value: Value,
-    },
-    /// Reference to the position in the incoming tuple(s).
-    /// Uses a relative pointer as a coordinate system:
-    /// - relational node (containing this reference)
-    /// - target(s) in the relational nodes list of children
-    /// - column position in the child(ren) output tuple
-    Reference {
-        /// Relational node ID that contains current reference.
-        parent: Option<NodeId>,
-        /// Targets in the relational node children list.
-        /// - Leaf nodes (relation scans): None.
-        /// - Union nodes: two elements (left and right).
-        /// - Other: single element.
-        targets: Option<Vec<usize>>,
-        /// Expression position in the input tuple (i.e. `Alias` column).
-        position: usize,
-        /// Referred column type in the input tuple.
-        col_type: Type,
-    },
-    /// Top of the tuple tree.
-    ///
-    /// If the current tuple is the output for some relational operator, it should
-    /// consist of the list of aliases. Otherwise (rows in selection filter
-    /// or in join condition) we don't require aliases in the list.
-    ///
-    ///
-    ///  Example: (a, b, 1).
-    Row {
-        /// A list of the alias expression node indexes in the plan node arena.
-        list: Vec<NodeId>,
-        /// Resulting data distribution of the tuple. Should be filled as a part
-        /// of the last "add Motion" transformation.
-        distribution: Option<Distribution>,
-    },
-    /// Stable function cannot modify the database and
-    /// is guaranteed to return the same results given
-    /// the same arguments for all rows within a single
-    /// statement.
-    ///
-    /// Example: `bucket_id("1")` (the number of buckets can be
-    /// changed only after restarting the cluster).
-    StableFunction {
-        /// Function name.
-        name: SmolStr,
-        /// Function arguments.
-        children: Vec<NodeId>,
-        /// Optional function feature.
-        feature: Option<FunctionFeature>,
-        /// Function return type.
-        func_type: Type,
-        /// Whether function is provided by tarantool,
-        /// when referencing these funcs from local
-        /// sql we must not use quotes.
-        /// Examples: aggregates, substr
-        is_system: bool,
-    },
-    /// Trim expression.
-    Trim {
-        /// Trim kind.
-        kind: Option<TrimKind>,
-        /// Trim string pattern to remove (it can be an expression).
-        pattern: Option<NodeId>,
-        /// Target expression to trim.
-        target: NodeId,
-    },
-    /// Unary expression returning boolean result.
-    Unary {
-        /// Unary operator.
-        op: operator::Unary,
-        /// Child expression node index in the plan node arena.
-        child: NodeId,
-    },
-    /// Argument of `count` aggregate in `count(*)` expression
-    CountAsterisk,
-    ExprInParentheses {
-        child: NodeId,
-    },
-    Case {
-        search_expr: Option<NodeId>,
-        when_blocks: Vec<(NodeId, NodeId)>,
-        else_expr: Option<NodeId>,
-    },
-}
-
 #[derive(Clone, Debug, Hash, Deserialize, PartialEq, Eq, Serialize)]
 pub enum FunctionFeature {
     /// Current function is an aggregate function and is marked as DISTINCT.
@@ -234,171 +59,14 @@ impl TrimKind {
     }
 }
 
-#[allow(dead_code)]
-impl Expression {
-    /// Gets current row distribution.
-    ///
-    /// # Errors
-    /// Returns `SbroadError` when the function is called on expression
-    /// other than `Row` or a node doesn't know its distribution yet.
-    pub fn distribution(&self) -> Result<&Distribution, SbroadError> {
-        if let Expression::Row { distribution, .. } = self {
-            let Some(dist) = distribution else {
-                return Err(SbroadError::Invalid(
-                    Entity::Distribution,
-                    Some("distribution is uninitialized".into()),
-                ));
-            };
-            return Ok(dist);
-        }
-        Err(SbroadError::Invalid(Entity::Expression, None))
-    }
-
-    /// Clone the row children list.
-    ///
-    /// # Errors
-    /// - node isn't `Row`
-    pub fn clone_row_list(&self) -> Result<Vec<NodeId>, SbroadError> {
-        match self {
-            Expression::Row { list, .. } => Ok(list.clone()),
-            _ => Err(SbroadError::Invalid(
-                Entity::Expression,
-                Some("node isn't Row type".into()),
-            )),
-        }
-    }
-
-    #[must_use]
-    pub fn is_aggregate_name(name: &str) -> bool {
-        // currently we support only simple aggregates
-        AggregateKind::new(name).is_some()
-    }
-
-    #[must_use]
-    pub fn is_aggregate_fun(&self) -> bool {
-        match self {
-            Expression::StableFunction { name, .. } => Expression::is_aggregate_name(name),
-            _ => false,
-        }
-    }
-
-    /// Get a reference to the row children list.
-    ///
-    /// # Errors
-    /// - node isn't `Row`
-    pub fn get_row_list(&self) -> Result<&[NodeId], SbroadError> {
-        match self {
-            Expression::Row { ref list, .. } => Ok(list),
-            _ => Err(SbroadError::Invalid(
-                Entity::Expression,
-                Some("node isn't Row type".into()),
-            )),
-        }
-    }
-
-    /// Get a mut reference to the row children list.
-    ///
-    /// # Errors
-    /// - node isn't `Row`
-    pub fn get_mut_row_list(&mut self) -> Result<&mut Vec<NodeId>, SbroadError> {
-        match self {
-            Expression::Row { ref mut list, .. } => Ok(list),
-            _ => Err(SbroadError::Invalid(
-                Entity::Expression,
-                Some("node isn't Row type".into()),
-            )),
-        }
-    }
-
-    /// Get a mutable reference to the row children list.
-    ///
-    /// # Errors
-    /// - node isn't `Row`
-    pub fn get_row_list_mut(&mut self) -> Result<&mut Vec<NodeId>, SbroadError> {
-        match self {
-            Expression::Row { ref mut list, .. } => Ok(list),
-            _ => Err(SbroadError::Invalid(
-                Entity::Expression,
-                Some("node isn't Row type".into()),
-            )),
-        }
-    }
-
-    /// Gets alias node name.
-    ///
-    /// # Errors
-    /// - node isn't `Alias`
-    pub fn get_alias_name(&self) -> Result<&str, SbroadError> {
-        match self {
-            Expression::Alias { name, .. } => Ok(name.as_str()),
-            _ => Err(SbroadError::Invalid(
-                Entity::Node,
-                Some("node is not Alias type".into()),
-            )),
-        }
-    }
-
-    /// Checks for distribution determination
-    ///
-    /// # Errors
-    /// - distribution isn't set
-    pub fn has_unknown_distribution(&self) -> Result<bool, SbroadError> {
-        let d = self.distribution()?;
-        Ok(d.is_unknown())
-    }
-
-    /// Gets relational node id containing the reference.
-    ///
-    /// # Errors
-    /// - node isn't reference type
-    /// - reference doesn't have a parent
-    pub fn get_parent(&self) -> Result<NodeId, SbroadError> {
-        if let Expression::Reference { parent, .. } = self {
-            return parent.ok_or_else(|| {
-                SbroadError::Invalid(Entity::Expression, Some("Reference has no parent".into()))
-            });
-        }
-        Err(SbroadError::Invalid(
-            Entity::Expression,
-            Some("node is not Reference type".into()),
-        ))
-    }
-
-    /// The node is a row expression.
-    #[must_use]
-    pub fn is_row(&self) -> bool {
-        matches!(self, Expression::Row { .. })
-    }
-    #[must_use]
-    pub fn is_arithmetic(&self) -> bool {
-        matches!(self, Expression::Arithmetic { .. })
-    }
-
-    /// Replaces parent in the reference node with the new one.
-    pub fn replace_parent_in_reference(&mut self, from_id: Option<NodeId>, to_id: Option<NodeId>) {
-        if let Expression::Reference { parent, .. } = self {
-            if *parent == from_id {
-                *parent = to_id;
-            }
-        }
-    }
-
-    /// Flushes parent in the reference node.
-    pub fn flush_parent_in_reference(&mut self) {
-        if let Expression::Reference { parent, .. } = self {
-            *parent = None;
-        }
-    }
-}
-
 impl Nodes {
     /// Adds exression covered with parentheses node.
     ///
     /// # Errors
     /// - child node is invalid
     pub(crate) fn add_covered_with_parentheses(&mut self, child: NodeId) -> NodeId {
-        let covered_with_parentheses = Expression::ExprInParentheses { child };
-        self.push(Node::Expression(covered_with_parentheses))
+        let covered_with_parentheses = ExprInParentheses { child };
+        self.push(covered_with_parentheses.into())
     }
 
     /// Adds alias node.
@@ -407,11 +75,11 @@ impl Nodes {
     /// - child node is invalid
     /// - name is empty
     pub fn add_alias(&mut self, name: &str, child: NodeId) -> Result<NodeId, SbroadError> {
-        let alias = Expression::Alias {
+        let alias = Alias {
             name: SmolStr::from(name),
             child,
         };
-        Ok(self.push(Node::Expression(alias)))
+        Ok(self.push(alias.into()))
     }
 
     /// Adds boolean node.
@@ -436,7 +104,7 @@ impl Nodes {
                 format_smolstr!("(right child of boolean node) from arena with index {right}"),
             )
         })?;
-        Ok(self.push(Node::Expression(Expression::Bool { left, op, right })))
+        Ok(self.push(BoolExpr { left, op, right }.into()))
     }
 
     /// Adds arithmetic node.
@@ -461,7 +129,7 @@ impl Nodes {
                 format_smolstr!("(right child of Arithmetic node) from arena with index {right:?}"),
             )
         })?;
-        Ok(self.push(Node::Expression(Expression::Arithmetic { left, op, right })))
+        Ok(self.push(ArithmeticExpr { left, op, right }.into()))
     }
 
     /// Adds reference node.
@@ -472,18 +140,18 @@ impl Nodes {
         position: usize,
         col_type: Type,
     ) -> NodeId {
-        let r = Expression::Reference {
+        let r = Reference {
             parent,
             targets,
             position,
             col_type,
         };
-        self.push(Node::Expression(r))
+        self.push(r.into())
     }
 
     /// Adds row node.
     pub fn add_row(&mut self, list: Vec<NodeId>, distribution: Option<Distribution>) -> NodeId {
-        self.push(Node::Expression(Expression::Row { list, distribution }))
+        self.push(Row { list, distribution }.into())
     }
 
     /// Adds unary boolean node.
@@ -501,7 +169,7 @@ impl Nodes {
                 format_smolstr!("from arena with index {child}"),
             )
         })?;
-        Ok(self.push(Node::Expression(Expression::Unary { op, child })))
+        Ok(self.push(UnaryExpr { op, child }.into()))
     }
 }
 
@@ -592,41 +260,43 @@ impl<'plan> Comparator<'plan> {
         if let Node::Expression(left) = l {
             if let Node::Expression(right) = r {
                 match left {
-                    Expression::Alias { .. } => {}
-                    Expression::CountAsterisk => {
-                        return Ok(matches!(right, Expression::CountAsterisk))
+                    Expression::Alias(_) => {}
+                    Expression::CountAsterisk(_) => {
+                        return Ok(matches!(right, Expression::CountAsterisk(_)))
                     }
-                    Expression::ExprInParentheses { child: l_child } => {
-                        if let Expression::ExprInParentheses { child: r_child } = right {
+                    Expression::ExprInParentheses(ExprInParentheses { child: l_child }) => {
+                        if let Expression::ExprInParentheses(ExprInParentheses { child: r_child }) =
+                            right
+                        {
                             return self.are_subtrees_equal(*l_child, *r_child);
                         }
                     }
-                    Expression::Bool {
+                    Expression::Bool(BoolExpr {
                         left: left_left,
                         op: op_left,
                         right: right_left,
-                    } => {
-                        if let Expression::Bool {
+                    }) => {
+                        if let Expression::Bool(BoolExpr {
                             left: left_right,
                             op: op_right,
                             right: right_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*op_left == *op_right
                                 && self.are_subtrees_equal(*left_left, *left_right)?
                                 && self.are_subtrees_equal(*right_left, *right_right)?);
                         }
                     }
-                    Expression::Case {
+                    Expression::Case(Case {
                         search_expr: search_expr_left,
                         when_blocks: when_blocks_left,
                         else_expr: else_expr_left,
-                    } => {
-                        if let Expression::Case {
+                    }) => {
+                        if let Expression::Case(Case {
                             search_expr: search_expr_right,
                             when_blocks: when_blocks_right,
                             else_expr: else_expr_right,
-                        } = right
+                        }) = right
                         {
                             let mut search_expr_equal = false;
                             if let (Some(search_expr_left), Some(search_expr_right)) =
@@ -654,58 +324,58 @@ impl<'plan> Comparator<'plan> {
                             return Ok(search_expr_equal && when_blocks_equal && else_expr_equal);
                         }
                     }
-                    Expression::Arithmetic {
+                    Expression::Arithmetic(ArithmeticExpr {
                         op: op_left,
                         left: l_left,
                         right: r_left,
-                    } => {
-                        if let Expression::Arithmetic {
+                    }) => {
+                        if let Expression::Arithmetic(ArithmeticExpr {
                             op: op_right,
                             left: l_right,
                             right: r_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*op_left == *op_right
                                 && self.are_subtrees_equal(*l_left, *l_right)?
                                 && self.are_subtrees_equal(*r_left, *r_right)?);
                         }
                     }
-                    Expression::Cast {
+                    Expression::Cast(Cast {
                         child: child_left,
                         to: to_left,
-                    } => {
-                        if let Expression::Cast {
+                    }) => {
+                        if let Expression::Cast(Cast {
                             child: child_right,
                             to: to_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*to_left == *to_right
                                 && self.are_subtrees_equal(*child_left, *child_right)?);
                         }
                     }
-                    Expression::Concat {
+                    Expression::Concat(Concat {
                         left: left_left,
                         right: right_left,
-                    } => {
-                        if let Expression::Concat {
+                    }) => {
+                        if let Expression::Concat(Concat {
                             left: left_right,
                             right: right_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(self.are_subtrees_equal(*left_left, *left_right)?
                                 && self.are_subtrees_equal(*right_left, *right_right)?);
                         }
                     }
-                    Expression::Trim {
+                    Expression::Trim(Trim {
                         kind: kind_left,
                         pattern: pattern_left,
                         target: target_left,
-                    } => {
-                        if let Expression::Trim {
+                    }) => {
+                        if let Expression::Trim(Trim {
                             kind: kind_right,
                             pattern: pattern_right,
                             target: target_right,
-                        } = right
+                        }) = right
                         {
                             match (pattern_left, pattern_right) {
                                 (Some(p_left), Some(p_right)) => {
@@ -723,31 +393,31 @@ impl<'plan> Comparator<'plan> {
                             }
                         }
                     }
-                    Expression::Constant { value: value_left } => {
-                        if let Expression::Constant { value: value_right } = right {
+                    Expression::Constant(Constant { value: value_left }) => {
+                        if let Expression::Constant(Constant { value: value_right }) = right {
                             return Ok(*value_left == *value_right);
                         }
                     }
-                    Expression::Reference { .. } => {
-                        if let Expression::Reference { .. } = right {
+                    Expression::Reference(_) => {
+                        if let Expression::Reference(_) = right {
                             return match self.policy {
                                 ReferencePolicy::ByAliases => {
                                     let alias_left =
-                                        self.plan.get_alias_from_reference_node(left)?;
+                                        self.plan.get_alias_from_reference_node(&left)?;
                                     let alias_right =
-                                        self.plan.get_alias_from_reference_node(right)?;
+                                        self.plan.get_alias_from_reference_node(&right)?;
                                     Ok(alias_left == alias_right)
                                 }
                                 ReferencePolicy::ByFields => Ok(left == right),
                             };
                         }
                     }
-                    Expression::Row {
+                    Expression::Row(Row {
                         list: list_left, ..
-                    } => {
-                        if let Expression::Row {
+                    }) => {
+                        if let Expression::Row(Row {
                             list: list_right, ..
-                        } = right
+                        }) = right
                         {
                             return Ok(list_left
                                 .iter()
@@ -755,20 +425,20 @@ impl<'plan> Comparator<'plan> {
                                 .all(|(l, r)| self.are_subtrees_equal(*l, *r).unwrap_or(false)));
                         }
                     }
-                    Expression::StableFunction {
+                    Expression::StableFunction(StableFunction {
                         name: name_left,
                         children: children_left,
                         feature: feature_left,
                         func_type: func_type_left,
                         is_system: is_aggr_left,
-                    } => {
-                        if let Expression::StableFunction {
+                    }) => {
+                        if let Expression::StableFunction(StableFunction {
                             name: name_right,
                             children: children_right,
                             feature: feature_right,
                             func_type: func_type_right,
                             is_system: is_aggr_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(name_left == name_right
                                 && feature_left == feature_right
@@ -779,14 +449,14 @@ impl<'plan> Comparator<'plan> {
                                 ));
                         }
                     }
-                    Expression::Unary {
+                    Expression::Unary(UnaryExpr {
                         op: op_left,
                         child: child_left,
-                    } => {
-                        if let Expression::Unary {
+                    }) => {
+                        if let Expression::Unary(UnaryExpr {
                             op: op_right,
                             child: child_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*op_left == *op_right
                                 && self.are_subtrees_equal(*child_left, *child_right)?);
@@ -819,18 +489,18 @@ impl<'plan> Comparator<'plan> {
             panic!("Hasher should have been set previously");
         };
         match node {
-            Expression::ExprInParentheses { child } => {
+            Expression::ExprInParentheses(ExprInParentheses { child }) => {
                 self.hash_for_child_expr(*child, depth);
             }
-            Expression::Alias { child, name } => {
+            Expression::Alias(Alias { child, name }) => {
                 name.hash(state);
                 self.hash_for_child_expr(*child, depth);
             }
-            Expression::Case {
+            Expression::Case(Case {
                 search_expr,
                 when_blocks,
                 else_expr,
-            } => {
+            }) => {
                 if let Some(search_expr) = search_expr {
                     self.hash_for_child_expr(*search_expr, depth);
                 }
@@ -842,47 +512,47 @@ impl<'plan> Comparator<'plan> {
                     self.hash_for_child_expr(*else_expr, depth);
                 }
             }
-            Expression::Bool { op, left, right } => {
+            Expression::Bool(BoolExpr { op, left, right }) => {
                 op.hash(state);
                 self.hash_for_child_expr(*left, depth);
                 self.hash_for_child_expr(*right, depth);
             }
-            Expression::Arithmetic { op, left, right } => {
+            Expression::Arithmetic(ArithmeticExpr { op, left, right }) => {
                 op.hash(state);
                 self.hash_for_child_expr(*left, depth);
                 self.hash_for_child_expr(*right, depth);
             }
-            Expression::Cast { child, to } => {
+            Expression::Cast(Cast { child, to }) => {
                 to.hash(state);
                 self.hash_for_child_expr(*child, depth);
             }
-            Expression::Concat { left, right } => {
+            Expression::Concat(Concat { left, right }) => {
                 self.hash_for_child_expr(*left, depth);
                 self.hash_for_child_expr(*right, depth);
             }
-            Expression::Trim {
+            Expression::Trim(Trim {
                 kind,
                 pattern,
                 target,
-            } => {
+            }) => {
                 kind.hash(state);
                 if let Some(pattern) = pattern {
                     self.hash_for_child_expr(*pattern, depth);
                 }
                 self.hash_for_child_expr(*target, depth);
             }
-            Expression::Constant { value } => {
+            Expression::Constant(Constant { value }) => {
                 value.hash(state);
             }
-            Expression::Reference {
+            Expression::Reference(Reference {
                 parent,
                 position,
                 targets,
                 col_type,
-            } => match self.policy {
+            }) => match self.policy {
                 ReferencePolicy::ByAliases => {
                     self.plan
-                        .get_alias_from_reference_node(node)
+                        .get_alias_from_reference_node(&node)
                         .unwrap_or("")
                         .hash(state);
                 }
@@ -893,18 +563,18 @@ impl<'plan> Comparator<'plan> {
                     col_type.hash(state);
                 }
             },
-            Expression::Row { list, .. } => {
+            Expression::Row(Row { list, .. }) => {
                 for child in list {
                     self.hash_for_child_expr(*child, depth);
                 }
             }
-            Expression::StableFunction {
+            Expression::StableFunction(StableFunction {
                 name,
                 children,
                 func_type,
                 feature,
                 is_system: is_aggr,
-            } => {
+            }) => {
                 feature.hash(state);
                 func_type.hash(state);
                 name.hash(state);
@@ -913,11 +583,11 @@ impl<'plan> Comparator<'plan> {
                     self.hash_for_child_expr(*child, depth);
                 }
             }
-            Expression::Unary { child, op } => {
+            Expression::Unary(UnaryExpr { child, op }) => {
                 op.hash(state);
                 self.hash_for_child_expr(*child, depth);
             }
-            Expression::CountAsterisk => {
+            Expression::CountAsterisk(_) => {
                 "CountAsterisk".hash(state);
             }
         }
@@ -980,7 +650,7 @@ impl ColumnPositionMap {
         for (pos, alias_id) in alias_ids.iter().enumerate() {
             let alias = plan.get_expression_node(*alias_id)?;
             let alias_name = SmolStr::from(alias.get_alias_name()?);
-            let scan_name = rel_node.scan_name(plan, pos)?.map(SmolStr::from);
+            let scan_name = plan.scan_name(rel_id, pos)?.map(SmolStr::from);
             // For query `select "a", "b" as "a" from (select "a", "b" from t)`
             // column entry "a" will have `Position::Multiple` so that if parent operator will
             // reference "a" we won't be able to identify which of these two columns
@@ -1296,7 +966,7 @@ impl Plan {
 
             let relational_op = self.get_relation_node(rel_child)?;
             let output_id = relational_op.output();
-            let child_node_row_list = self.get_row_list(output_id)?.to_vec();
+            let child_node_row_list = self.get_row_list(output_id)?.clone();
 
             let mut indices: Vec<usize> = Vec::new();
             match columns_spec {
@@ -1312,7 +982,7 @@ impl Plan {
                         indices.push(index);
                     }
                 }
-                ColumnsRetrievalSpec::Indices(idx) => indices = idx.clone(),
+                ColumnsRetrievalSpec::Indices(idx) => indices.clone_from(&idx),
             };
 
             let exclude_positions = column_positions_to_exclude(rel_child)?;
@@ -1359,7 +1029,6 @@ impl Plan {
             let alias_expr = self.get_expression_node(alias_node_id)?;
             let alias_name = SmolStr::from(alias_expr.get_alias_name()?);
             let col_type = alias_expr.calculate_type(self)?;
-
             let r_id = self.nodes.add_ref(None, Some(new_targets), pos, col_type);
             if need_aliases {
                 let a_id = self.nodes.add_alias(&alias_name, r_id)?;
@@ -1607,21 +1276,21 @@ impl Plan {
         &self,
         ref_id: NodeId,
     ) -> Result<&NodeId, SbroadError> {
-        if let Node::Expression(Expression::Reference {
+        if let Node::Expression(Expression::Reference(Reference {
             targets, parent, ..
-        }) = self.get_node(ref_id)?
+        })) = self.get_node(ref_id)?
         {
             let Some(referred_rel_id) = parent else {
                 return Err(SbroadError::NotFound(
                     Entity::Node,
-                    format_smolstr!("that is Reference ({ref_id:?}) parent"),
+                    format_smolstr!("that is Reference ({ref_id}) parent"),
                 ));
             };
             let rel = self.get_relation_node(*referred_rel_id)?;
             if let Relational::Insert { .. } = rel {
                 return Ok(referred_rel_id);
             }
-            let children = rel.children();
+            let children = self.children(*referred_rel_id);
             match targets {
                 None => {
                     return Err(SbroadError::UnexpectedNumberOfValues(
@@ -1667,7 +1336,7 @@ impl Plan {
         row_id: NodeId,
     ) -> Result<HashSet<NodeId, RandomState>, SbroadError> {
         let row = self.get_expression_node(row_id)?;
-        let capacity = if let Expression::Row { list, .. } = row {
+        let capacity = if let Expression::Row(Row { list, .. }) = row {
             list.len()
         } else {
             return Err(SbroadError::Invalid(
@@ -1691,17 +1360,16 @@ impl Plan {
         // We don't expect much relational references in a row (5 is a reasonable number).
         let mut rel_nodes: HashSet<NodeId, RandomState> =
             HashSet::with_capacity_and_hasher(5, RandomState::new());
-        for level_node in nodes {
-            let id = level_node.1;
+        for LevelNode(_, id) in nodes {
             let reference = self.get_expression_node(id)?;
-            if let Expression::Reference {
+            if let Expression::Reference(Reference {
                 targets, parent, ..
-            } = reference
+            }) = reference
             {
                 let referred_rel_id = parent.ok_or_else(|| {
                     SbroadError::NotFound(
                         Entity::Node,
-                        format_smolstr!("that is Reference ({id:?}) parent"),
+                        format_smolstr!("that is Reference ({id}) parent"),
                     )
                 })?;
                 let rel = self.get_relation_node(referred_rel_id)?;
@@ -1724,7 +1392,7 @@ impl Plan {
         let Ok(node) = self.get_expression_node(node_id) else {
             return false;
         };
-        if let Expression::Bool { left, op, right } = node {
+        if let Expression::Bool(BoolExpr { left, op, right }) = node {
             if *op != Bool::Eq {
                 return false;
             }
@@ -1752,15 +1420,17 @@ impl Plan {
     pub fn is_trivalent(&self, expr_id: NodeId) -> Result<bool, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
-            Expression::Bool { .. }
-            | Expression::Arithmetic { .. }
-            | Expression::Unary { .. }
-            | Expression::Constant {
+            Expression::Bool(_)
+            | Expression::Arithmetic(_)
+            | Expression::Unary(_)
+            | Expression::Constant(Constant {
                 value: Value::Boolean(_) | Value::Null,
                 ..
-            } => return Ok(true),
-            Expression::ExprInParentheses { child } => return self.is_trivalent(*child),
-            Expression::Row { list, .. } => {
+            }) => return Ok(true),
+            Expression::ExprInParentheses(ExprInParentheses { child }) => {
+                return self.is_trivalent(*child)
+            }
+            Expression::Row(Row { list, .. }) => {
                 if let (Some(inner_id), None) = (list.first(), list.get(1)) {
                     return self.is_trivalent(*inner_id);
                 }
@@ -1778,7 +1448,7 @@ impl Plan {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
             Expression::Reference { .. } => return Ok(true),
-            Expression::Row { list, .. } => {
+            Expression::Row(Row { list, .. }) => {
                 if let (Some(inner_id), None) = (list.first(), list.get(1)) {
                     return self.is_ref(*inner_id);
                 }
@@ -1801,7 +1471,7 @@ impl Plan {
         child_num: usize,
     ) -> Result<Value, SbroadError> {
         let node = self.get_expression_node(row_id)?;
-        if let Expression::Row { list, .. } = node {
+        if let Expression::Row(Row { list, .. }) = node {
             let const_node_id = list.get(child_num).ok_or_else(|| {
                 SbroadError::NotFound(Entity::Node, format_smolstr!("{child_num}"))
             })?;
@@ -1841,9 +1511,8 @@ impl Plan {
         subtree.populate_nodes(node_id);
         let references = subtree.take_nodes();
         drop(subtree);
-        for level_node in references {
-            let id = level_node.1;
-            let node = self.get_mut_expression_node(id)?;
+        for LevelNode(_, id) in references {
+            let mut node = self.get_mut_expression_node(id)?;
             node.replace_parent_in_reference(from_id, to_id);
         }
         Ok(())
@@ -1869,14 +1538,43 @@ impl Plan {
         subtree.populate_nodes(node_id);
         let references = subtree.take_nodes();
         drop(subtree);
-        for level_node in references {
-            let id = level_node.1;
-            let node = self.get_mut_expression_node(id)?;
+        for LevelNode(_, id) in references {
+            let mut node = self.get_mut_expression_node(id)?;
             node.flush_parent_in_reference();
         }
         Ok(())
     }
 }
 
+impl Expression<'_> {
+    /// Get a reference to the row children list.
+    ///
+    /// # Errors
+    /// - node isn't `Row`
+    pub fn get_row_list(&self) -> Result<&Vec<NodeId>, SbroadError> {
+        match self {
+            Expression::Row(Row { ref list, .. }) => Ok(list),
+            _ => Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("node isn't Row type".into()),
+            )),
+        }
+    }
+
+    /// Gets alias node name.
+    ///
+    /// # Errors
+    /// - node isn't `Alias`
+    pub fn get_alias_name(&self) -> Result<&str, SbroadError> {
+        match self {
+            Expression::Alias(Alias { name, .. }) => Ok(name.as_str()),
+            _ => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("node is not Alias type".into()),
+            )),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests;
diff --git a/sbroad-core/src/ir/expression/cast.rs b/sbroad-core/src/ir/expression/cast.rs
index d4a07d55b..92a5da90e 100644
--- a/sbroad-core/src/ir/expression/cast.rs
+++ b/sbroad-core/src/ir/expression/cast.rs
@@ -2,13 +2,13 @@ use std::fmt::{Display, Formatter};
 
 use crate::errors::{Entity, SbroadError};
 use crate::frontend::sql::ast::Rule;
-use crate::ir::expression::Expression;
+use crate::ir::node::Cast;
 use crate::ir::relation::Type as RelationType;
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 
-use super::NodeId;
+use super::{MutNode, NodeId};
 
 #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
 pub enum Type {
@@ -133,15 +133,15 @@ impl Plan {
     /// # Errors
     /// - Child node is not of the expression type.
     pub fn add_cast(&mut self, expr_id: NodeId, to_type: Type) -> Result<NodeId, SbroadError> {
-        let cast_expr = Expression::Cast {
+        let cast_expr = Cast {
             child: expr_id,
             to: to_type,
         };
-        let cast_id = self.nodes.push(Node::Expression(cast_expr));
+        let cast_id = self.nodes.push(cast_expr.into());
 
         let child_plan_node = self.get_mut_node(expr_id)?;
-        if let Node::Parameter(ref mut ty) = child_plan_node {
-            *ty = Some(to_type.as_relation_type());
+        if let MutNode::Parameter(ty) = child_plan_node {
+            ty.param_type = Some(to_type.as_relation_type());
         }
 
         Ok(cast_id)
diff --git a/sbroad-core/src/ir/expression/concat.rs b/sbroad-core/src/ir/expression/concat.rs
index 09e637988..c469fee91 100644
--- a/sbroad-core/src/ir/expression/concat.rs
+++ b/sbroad-core/src/ir/expression/concat.rs
@@ -1,6 +1,6 @@
 use crate::errors::SbroadError;
-use crate::ir::expression::Expression;
-use crate::ir::{Node, Plan};
+use crate::ir::node::Concat;
+use crate::ir::Plan;
 
 use super::NodeId;
 
@@ -14,10 +14,13 @@ impl Plan {
         for child_id in &[left_id, right_id] {
             self.get_expression_node(*child_id)?;
         }
-        let concat_id = self.nodes.push(Node::Expression(Expression::Concat {
-            left: left_id,
-            right: right_id,
-        }));
+        let concat_id = self.nodes.push(
+            Concat {
+                left: left_id,
+                right: right_id,
+            }
+            .into(),
+        );
         Ok(concat_id)
     }
 }
diff --git a/sbroad-core/src/ir/expression/types.rs b/sbroad-core/src/ir/expression/types.rs
index 0122fe56c..cd0c8e71a 100644
--- a/sbroad-core/src/ir/expression/types.rs
+++ b/sbroad-core/src/ir/expression/types.rs
@@ -2,10 +2,13 @@ use smol_str::{format_smolstr, ToSmolStr};
 
 use crate::{
     errors::{Entity, SbroadError},
-    ir::{expression::Expression, relation::Type, Node, Plan},
+    ir::{relation::Type, Plan},
 };
 
-use super::NodeId;
+use super::{
+    Alias, ArithmeticExpr, Case, Cast, Constant, ExprInParentheses, Expression, MutExpression,
+    Node, NodeId, Reference, Row, StableFunction,
+};
 
 impl Plan {
     fn get_node_type(&self, node_id: NodeId) -> Result<Type, SbroadError> {
@@ -19,7 +22,7 @@ impl Plan {
             )),
             // Parameter nodes must recalculate their type during
             // binding (see `bind_params` function).
-            Node::Parameter(ty) => Ok(ty.clone().unwrap_or(Type::Scalar)),
+            Node::Parameter(ty) => Ok(ty.param_type.clone().unwrap_or(Type::Scalar)),
             Node::Ddl(_) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some("DDL node has no type".to_smolstr()),
@@ -28,6 +31,10 @@ impl Plan {
                 Entity::Node,
                 Some("ACL node has no type".to_smolstr()),
             )),
+            Node::Invalid(_) => Err(SbroadError::Invalid(
+                Entity::Node,
+                Some("Invalid node has no type".to_smolstr()),
+            )),
             Node::Block(_) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some("code block node has no type".to_smolstr()),
@@ -36,7 +43,7 @@ impl Plan {
     }
 }
 
-impl Expression {
+impl Expression<'_> {
     /// Calculate the type of the expression.
     ///
     /// # Errors
@@ -46,11 +53,11 @@ impl Expression {
     /// - Plan is in inconsistent state
     pub fn calculate_type(&self, plan: &Plan) -> Result<Type, SbroadError> {
         match self {
-            Expression::Case {
+            Expression::Case(Case {
                 when_blocks,
                 else_expr,
                 ..
-            } => {
+            }) => {
                 let mut case_type = None;
                 let check_types_corresponds = |case_type: &Type, ret_expr_type: &Type| {
                     if case_type != ret_expr_type {
@@ -86,13 +93,14 @@ impl Expression {
                 }
                 Ok(case_type_unwrapped)
             }
-            Expression::Alias { child, .. } | Expression::ExprInParentheses { child } => {
+            Expression::Alias(Alias { child, .. })
+            | Expression::ExprInParentheses(ExprInParentheses { child }) => {
                 plan.get_node_type(*child)
             }
-            Expression::Bool { .. } | Expression::Unary { .. } => Ok(Type::Boolean),
-            Expression::Arithmetic {
+            Expression::Bool(_) | Expression::Unary(_) => Ok(Type::Boolean),
+            Expression::Arithmetic(ArithmeticExpr {
                 left, right, op, ..
-            } => {
+            }) => {
                 let left_type = plan.get_node_type(*left)?;
                 let right_type = plan.get_node_type(*right)?;
                 match (&left_type, &right_type) {
@@ -112,11 +120,11 @@ impl Expression {
                     )),
                 }
             }
-            Expression::Cast { to, .. } => Ok(to.as_relation_type()),
-            Expression::Trim { .. } | Expression::Concat { .. } => Ok(Type::String),
-            Expression::Constant { value, .. } => Ok(value.get_type()),
-            Expression::Reference { col_type, .. } => Ok(col_type.clone()),
-            Expression::Row { list, .. } => {
+            Expression::Cast(Cast { to, .. }) => Ok(to.as_relation_type()),
+            Expression::Trim(_) | Expression::Concat(_) => Ok(Type::String),
+            Expression::Constant(Constant { value, .. }) => Ok(value.get_type()),
+            Expression::Reference(Reference { col_type, .. }) => Ok(col_type.clone()),
+            Expression::Row(Row { list, .. }) => {
                 if let (Some(expr_id), None) = (list.first(), list.get(1)) {
                     let expr = plan.get_expression_node(*expr_id)?;
                     expr.calculate_type(plan)
@@ -124,12 +132,12 @@ impl Expression {
                     Ok(Type::Array)
                 }
             }
-            Expression::StableFunction {
+            Expression::StableFunction(StableFunction {
                 name,
                 func_type,
                 children,
                 ..
-            } => {
+            }) => {
                 // min/max functions have a scalar type, which means that their actual type can be
                 // inferred from the arguments.
                 if let "max" | "min" = name.as_str() {
@@ -142,7 +150,7 @@ impl Expression {
                     Ok(func_type.clone())
                 }
             }
-            Expression::CountAsterisk => Ok(Type::Integer),
+            Expression::CountAsterisk(_) => Ok(Type::Integer),
         }
     }
 
@@ -163,12 +171,12 @@ impl Expression {
     /// # Errors
     /// - if the reference is invalid;
     pub fn recalculate_type(&self, plan: &Plan) -> Result<Type, SbroadError> {
-        if let Expression::Reference {
+        if let Expression::Reference(Reference {
             parent,
             targets,
             position,
             ..
-        } = self
+        }) = self
         {
             let parent_id = parent.ok_or_else(|| {
                 SbroadError::Invalid(
@@ -206,9 +214,11 @@ impl Expression {
         }
         self.calculate_type(plan)
     }
+}
 
+impl MutExpression<'_> {
     pub fn set_ref_type(&mut self, new_type: Type) {
-        if let Expression::Reference { col_type, .. } = self {
+        if let MutExpression::Reference(Reference { col_type, .. }) = self {
             *col_type = new_type;
         }
     }
diff --git a/sbroad-core/src/ir/function.rs b/sbroad-core/src/ir/function.rs
index 5732bef6d..0e31fadb5 100644
--- a/sbroad-core/src/ir/function.rs
+++ b/sbroad-core/src/ir/function.rs
@@ -1,13 +1,13 @@
 use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::to_user;
 use crate::ir::aggregates::AggregateKind;
-use crate::ir::expression::Expression;
+use crate::ir::node::{NodeId, StableFunction};
 use crate::ir::relation::Type;
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 
-use super::expression::{FunctionFeature, NodeId};
+use super::expression::FunctionFeature;
 
 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
 pub enum Behavior {
@@ -70,14 +70,14 @@ impl Plan {
                 Some(format_smolstr!("function {} is not stable", function.name)),
             ));
         }
-        let func_expr = Expression::StableFunction {
+        let func_expr = StableFunction {
             name: function.name.to_smolstr(),
             children,
             feature,
             func_type: function.func_type.clone(),
             is_system: function.is_system,
         };
-        let func_id = self.nodes.push(Node::Expression(func_expr));
+        let func_id = self.nodes.push(func_expr.into());
         Ok(func_id)
     }
 
@@ -132,14 +132,14 @@ impl Plan {
         } else {
             None
         };
-        let func_expr = Expression::StableFunction {
+        let func_expr = StableFunction {
             name: function.to_lowercase().to_smolstr(),
             children,
             feature,
             func_type: Type::from(kind),
             is_system: true,
         };
-        let id = self.nodes.push(Node::Expression(func_expr));
+        let id = self.nodes.push(func_expr.into());
         Ok(id)
     }
 }
diff --git a/sbroad-core/src/ir/helpers.rs b/sbroad-core/src/ir/helpers.rs
index 7c59ef618..68a239594 100644
--- a/sbroad-core/src/ir/helpers.rs
+++ b/sbroad-core/src/ir/helpers.rs
@@ -4,15 +4,21 @@ use smol_str::{SmolStr, ToSmolStr};
 
 use crate::backend::sql::tree::{SyntaxData, SyntaxPlan};
 use crate::errors::{Action, Entity, SbroadError};
-use crate::ir::expression::Expression;
-use crate::ir::operator::{OrderByEntity, Relational};
+use crate::ir::node::{
+    Alias, BoolExpr, Case, Constant, Delete, Except, ExprInParentheses, GroupBy, Having, Insert,
+    Intersect, Join, Motion, NodeId, OrderBy, Projection, Reference, Row, ScanCte, ScanRelation,
+    ScanSubQuery, Selection, UnaryExpr, Union, UnionAll, Update, Values, ValuesRow,
+};
+use crate::ir::operator::OrderByEntity;
 use crate::ir::tree::traversal::{PostOrder, EXPR_CAPACITY};
 use crate::ir::{Node, Plan};
 use std::collections::hash_map::DefaultHasher;
 use std::fmt::Write;
 use std::hash::BuildHasher;
 
-use super::expression::NodeId;
+use super::node::expression::Expression;
+use super::node::relational::Relational;
+use super::node::Limit;
 
 /// Helper macros to build a hash map or set
 /// from the list of arguments.
@@ -83,7 +89,7 @@ impl Plan {
         if let Ok(expr) = expr_try {
             write!(buf, "expression: ")?;
             match expr {
-                Expression::Alias { name, child } => {
+                Expression::Alias(Alias { name, child }) => {
                     let child_node = self.get_node(*child).expect("Alias must have a child node");
                     let child = match child_node {
                         Node::Expression(child_expr) => format!("{child_expr:?}"),
@@ -94,16 +100,16 @@ impl Plan {
                     };
                     writeln!(buf, "Alias [name = {name}, child = {child}]")?;
                 }
-                Expression::ExprInParentheses { child } => {
+                Expression::ExprInParentheses(ExprInParentheses { child }) => {
                     writeln!(buf, "Parentheses")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Child")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *child)?;
                 }
-                Expression::Case {
+                Expression::Case(Case {
                     search_expr,
                     when_blocks,
                     else_expr,
-                } => {
+                }) => {
                     writeln!(buf, "Case")?;
                     if let Some(search_expr) = search_expr {
                         writeln_with_tabulation(buf, tabulation_number + 1, "Search_expr")?;
@@ -120,24 +126,24 @@ impl Plan {
                         self.formatted_arena_node(buf, tabulation_number + 1, *else_expr)?;
                     }
                 }
-                Expression::Bool { op, left, right } => {
+                Expression::Bool(BoolExpr { op, left, right }) => {
                     writeln!(buf, "Bool [op: {op}]")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Left child")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *left)?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Right child")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *right)?;
                 }
-                Expression::Constant { value } => {
+                Expression::Constant(Constant { value }) => {
                     writeln!(buf, "Constant [value = {value}]")?;
                 }
-                Expression::CountAsterisk => writeln!(buf, "CountAsterisk")?,
-                Expression::Reference {
+                Expression::CountAsterisk(_) => writeln!(buf, "CountAsterisk")?,
+                Expression::Reference(Reference {
                     targets,
                     position,
                     parent,
                     col_type,
-                } => {
-                    let alias_name = self.get_alias_from_reference_node(expr).unwrap();
+                }) => {
+                    let alias_name = self.get_alias_from_reference_node(&expr).unwrap();
 
                     writeln!(buf, "Reference")?;
                     writeln_with_tabulation(
@@ -150,8 +156,8 @@ impl Plan {
                     let rel_id = self.get_relational_from_reference_node(node_id);
                     if let Ok(rel_id) = rel_id {
                         let rel_node = self.get_relation_node(*rel_id);
-                        if let Ok(rel_node) = rel_node {
-                            if let Ok(Some(name)) = rel_node.scan_name(self, *position) {
+                        if rel_node.is_ok() {
+                            if let Ok(Some(name)) = self.scan_name(*rel_id, *position) {
                                 writeln_with_tabulation(
                                     buf,
                                     tabulation_number + 1,
@@ -185,23 +191,23 @@ impl Plan {
                         format!("Column type: {col_type}").as_str(),
                     )?;
                 }
-                Expression::Row { list, distribution } => {
+                Expression::Row(Row { list, distribution }) => {
                     writeln!(buf, "Row [distribution = {distribution:?}]")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "List:")?;
                     for value in list {
                         self.formatted_arena_node(buf, tabulation_number + 2, *value)?;
                     }
                 }
-                Expression::Cast { .. } => writeln!(buf, "Cast")?,
-                Expression::Trim { .. } => writeln!(buf, "Trim")?,
-                Expression::Concat { .. } => writeln!(buf, "Concat")?,
-                Expression::StableFunction { .. } => writeln!(buf, "StableFunction")?,
-                Expression::Unary { op, child } => {
+                Expression::Cast(_) => writeln!(buf, "Cast")?,
+                Expression::Trim(_) => writeln!(buf, "Trim")?,
+                Expression::Concat(_) => writeln!(buf, "Concat")?,
+                Expression::StableFunction(_) => writeln!(buf, "StableFunction")?,
+                Expression::Unary(UnaryExpr { op, child }) => {
                     writeln!(buf, "Unary [op: {op}]")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Child")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *child)?;
                 }
-                Expression::Arithmetic { .. } => writeln!(buf, "Arithmetic")?,
+                Expression::Arithmetic(_) => writeln!(buf, "Arithmetic")?,
             };
         }
         Ok(())
@@ -224,9 +230,9 @@ impl Plan {
             write!(buf, "relation: ")?;
             // Print relation name and specific info.
             match relation {
-                Relational::ScanRelation {
+                Relational::ScanRelation(ScanRelation {
                     alias, relation, ..
-                } => {
+                }) => {
                     writeln!(buf, "ScanRelation")?;
                     writeln_with_tabulation(
                         buf,
@@ -241,15 +247,15 @@ impl Plan {
                         )?;
                     }
                 }
-                Relational::Join { condition, .. } => {
+                Relational::Join(Join { condition, .. }) => {
                     writeln!(buf, "InnerJoin")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Condition:")?;
                     self.formatted_arena_node(buf, tabulation_number + 2, *condition)?;
                 }
-                Relational::Projection { .. } => {
+                Relational::Projection(_) => {
                     writeln!(buf, "Projection")?;
                 }
-                Relational::ScanCte { alias, .. } => {
+                Relational::ScanCte(ScanCte { alias, .. }) => {
                     writeln!(buf, "ScanCte")?;
                     if !alias.is_empty() {
                         writeln_with_tabulation(
@@ -259,7 +265,7 @@ impl Plan {
                         )?;
                     }
                 }
-                Relational::ScanSubQuery { alias, .. } => {
+                Relational::ScanSubQuery(ScanSubQuery { alias, .. }) => {
                     writeln!(buf, "ScanSubQuery")?;
                     if let Some(alias) = alias {
                         if !alias.is_empty() {
@@ -271,23 +277,23 @@ impl Plan {
                         }
                     }
                 }
-                Relational::Selection {
+                Relational::Selection(Selection {
                     children: _,
                     filter,
                     output: _,
-                } => {
+                }) => {
                     writeln!(buf, "Selection")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Filter")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *filter)?;
                 }
-                Relational::Having { filter, .. } => {
+                Relational::Having(Having { filter, .. }) => {
                     writeln!(buf, "Having")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Filter")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *filter)?;
                 }
-                Relational::GroupBy {
+                Relational::GroupBy(GroupBy {
                     gr_cols, is_final, ..
-                } => {
+                }) => {
                     writeln!(buf, "GroupBy [is_final = {is_final}]")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Gr_cols:")?;
                     for gr_col in gr_cols {
@@ -295,14 +301,14 @@ impl Plan {
                         let text = if let Ok(gl_col_expr) = gl_col_expr {
                             format!("Gr_col: {gl_col_expr:?}")
                         } else {
-                            format!("Gr_col: {gr_col:?}")
+                            format!("Gr_col: {gr_col}")
                         };
                         writeln_with_tabulation(buf, tabulation_number + 2, text.as_str())?;
                     }
                 }
-                Relational::OrderBy {
+                Relational::OrderBy(OrderBy {
                     order_by_elements, ..
-                } => {
+                }) => {
                     writeln!(buf, "OrderBy")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Order_by_elements:")?;
                     for element in order_by_elements {
@@ -322,52 +328,51 @@ impl Plan {
                     }
                 }
                 Relational::Values { .. } => writeln!(buf, "Values")?,
-                Relational::ValuesRow { data, .. } => {
+                Relational::ValuesRow(ValuesRow { data, .. }) => {
                     writeln!(buf, "ValuesRow")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Data")?;
                     self.formatted_arena_node(buf, tabulation_number + 1, *data)?;
                 }
-                Relational::Motion { policy, .. } => {
+                Relational::Motion(Motion { policy, .. }) => {
                     writeln!(buf, "Motion [policy = {policy:?}]")?;
                 }
                 Relational::Union { .. } => writeln!(buf, "Union")?,
                 Relational::UnionAll { .. } => writeln!(buf, "UnionAll")?,
-                Relational::Update {
+                Relational::Update(Update {
                     relation,
                     update_columns_map,
                     ..
-                } => {
+                }) => {
                     writeln!(buf, "Update")?;
                     writeln_with_tabulation(buf, tabulation_number + 1, "Update columns map:")?;
                     for (rel_pos, proj_pos) in update_columns_map {
                         writeln_with_tabulation(buf, tabulation_number + 2, format!("Update {relation} column on pos {rel_pos} to child projection column on pos {proj_pos}").as_str())?;
                     }
                 }
-                Relational::Delete { .. } => writeln!(buf, "Delete")?,
-                Relational::Insert { .. } => writeln!(buf, "Insert")?,
-                Relational::Intersect { .. } => writeln!(buf, "Intersect")?,
-                Relational::Except { .. } => writeln!(buf, "Except")?,
-                Relational::Limit { limit, .. } => writeln!(buf, "Limit {limit}")?,
+                Relational::Delete(_) => writeln!(buf, "Delete")?,
+                Relational::Insert(_) => writeln!(buf, "Insert")?,
+                Relational::Intersect(_) => writeln!(buf, "Intersect")?,
+                Relational::Except(_) => writeln!(buf, "Except")?,
+                Relational::Limit(Limit { limit, .. }) => writeln!(buf, "Limit {limit}")?,
             }
             // Print children.
             match relation {
-                node @ (Relational::Join { .. }
-                | Relational::Projection { .. }
-                | Relational::Except { .. }
-                | Relational::Delete { .. }
-                | Relational::Insert { .. }
-                | Relational::Intersect { .. }
-                | Relational::ScanSubQuery { .. }
-                | Relational::Selection { .. }
-                | Relational::Values { .. }
-                | Relational::Motion { .. }
-                | Relational::Union { .. }
-                | Relational::UnionAll { .. }
-                | Relational::Update { .. }
-                | Relational::Having { .. }
-                | Relational::GroupBy { .. }
-                | Relational::ValuesRow { .. }
-                | Relational::Limit { .. }) => {
+                ref node @ (Relational::Join(_)
+                | Relational::Projection(_)
+                | Relational::Except(_)
+                | Relational::Delete(_)
+                | Relational::Insert(_)
+                | Relational::Intersect(_)
+                | Relational::ScanSubQuery(_)
+                | Relational::Selection(_)
+                | Relational::Values(_)
+                | Relational::Motion(_)
+                | Relational::Union(_)
+                | Relational::UnionAll(_)
+                | Relational::Update(_)
+                | Relational::Having(_)
+                | Relational::GroupBy(_)
+                | Relational::ValuesRow(_)) => {
                     writeln_with_tabulation(buf, tabulation_number + 1, "Children:")?;
                     for child in &node.children() {
                         writeln_with_tabulation(
@@ -377,7 +382,9 @@ impl Plan {
                         )?;
                     }
                 }
-                Relational::OrderBy { child, .. } | Relational::ScanCte { child, .. } => {
+                Relational::Limit(Limit { child, .. })
+                | Relational::OrderBy(OrderBy { child, .. })
+                | Relational::ScanCte(ScanCte { child, .. }) => {
                     writeln_with_tabulation(buf, tabulation_number + 1, "Children:")?;
                     writeln_with_tabulation(
                         buf,
@@ -391,26 +398,26 @@ impl Plan {
             }
             // Print output.
             match relation {
-                Relational::ScanRelation { output, .. }
-                | Relational::Join { output, .. }
-                | Relational::Except { output, .. }
-                | Relational::Delete { output, .. }
-                | Relational::Insert { output, .. }
-                | Relational::Intersect { output, .. }
-                | Relational::Projection { output, .. }
-                | Relational::ScanCte { output, .. }
-                | Relational::ScanSubQuery { output, .. }
-                | Relational::GroupBy { output, .. }
-                | Relational::OrderBy { output, .. }
-                | Relational::Selection { output, .. }
-                | Relational::Having { output, .. }
-                | Relational::Values { output, .. }
-                | Relational::Motion { output, .. }
-                | Relational::Union { output, .. }
-                | Relational::UnionAll { output, .. }
-                | Relational::Update { output, .. }
-                | Relational::ValuesRow { output, .. }
-                | Relational::Limit { output, .. } => {
+                Relational::ScanRelation(ScanRelation { output, .. })
+                | Relational::Join(Join { output, .. })
+                | Relational::Except(Except { output, .. })
+                | Relational::Delete(Delete { output, .. })
+                | Relational::Insert(Insert { output, .. })
+                | Relational::Intersect(Intersect { output, .. })
+                | Relational::Projection(Projection { output, .. })
+                | Relational::ScanCte(ScanCte { output, .. })
+                | Relational::ScanSubQuery(ScanSubQuery { output, .. })
+                | Relational::GroupBy(GroupBy { output, .. })
+                | Relational::OrderBy(OrderBy { output, .. })
+                | Relational::Selection(Selection { output, .. })
+                | Relational::Having(Having { output, .. })
+                | Relational::Values(Values { output, .. })
+                | Relational::Motion(Motion { output, .. })
+                | Relational::Union(Union { output, .. })
+                | Relational::UnionAll(UnionAll { output, .. })
+                | Relational::Update(Update { output, .. })
+                | Relational::ValuesRow(ValuesRow { output, .. })
+                | Relational::Limit(Limit { output, .. }) => {
                     writeln_with_tabulation(
                         buf,
                         tabulation_number + 1,
diff --git a/sbroad-core/src/ir/helpers/tests.rs b/sbroad-core/src/ir/helpers/tests.rs
index a910c1fa6..dea18673e 100644
--- a/sbroad-core/src/ir/helpers/tests.rs
+++ b/sbroad-core/src/ir/helpers/tests.rs
@@ -1,4 +1,7 @@
-use crate::ir::{expression::NodeId, transformation::helpers::sql_to_optimized_ir, ArenaType};
+use crate::{
+    ir::node::NodeId,
+    ir::{transformation::helpers::sql_to_optimized_ir, ArenaType},
+};
 use pretty_assertions::assert_eq;
 
 #[test]
@@ -11,26 +14,26 @@ fn simple_select() {
     let mut expected_arena = String::new();
     expected_arena.push_str(
         r#"---------------------------------------------
-[id: 11] relation: ScanRelation
+[id: 664] relation: ScanRelation
 	Relation: hash_testing
 	[No children]
-	Output_id: 10
-		[id: 10] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
+	Output_id: 564
+		[id: 564] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 1] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 0, col_type: Integer }]
-				[id: 3] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 1, col_type: String }]
-				[id: 5] expression: Alias [name = product_units, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 2, col_type: Boolean }]
-				[id: 7] expression: Alias [name = sys_op, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 3, col_type: Unsigned }]
-				[id: 9] expression: Alias [name = bucket_id, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 4, col_type: Unsigned }]
+				[id: 032] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer })]
+				[id: 132] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 1, col_type: String })]
+				[id: 232] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean })]
+				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned })]
+				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 15] relation: Projection
+[id: 964] relation: Projection
 	Children:
-		Child_id = 11
-	Output_id: 14
-		[id: 14] expression: Row [distribution = Some(Any)]
+		Child_id = 664
+	Output_id: 864
+		[id: 864] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 13] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 15, arena_type: Default }), targets: Some([0]), position: 1, col_type: String }]
+				[id: 532] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String })]
 ---------------------------------------------
 "#);
 
@@ -50,117 +53,117 @@ fn simple_join() {
     let mut expected_arena = String::new();
     expected_arena.push_str(
         r#"---------------------------------------------
-[id: 11] relation: ScanRelation
+[id: 664] relation: ScanRelation
 	Relation: test_space
 	[No children]
-	Output_id: 10
-		[id: 10] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+	Output_id: 564
+		[id: 564] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 1] expression: Alias [name = id, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 0, col_type: Unsigned }]
-				[id: 3] expression: Alias [name = sysFrom, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 1, col_type: Unsigned }]
-				[id: 5] expression: Alias [name = FIRST_NAME, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 2, col_type: String }]
-				[id: 7] expression: Alias [name = sys_op, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 3, col_type: Unsigned }]
-				[id: 9] expression: Alias [name = bucket_id, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 4, col_type: Unsigned }]
+				[id: 032] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 0, col_type: Unsigned })]
+				[id: 132] expression: Alias [name = sysFrom, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 1, col_type: Unsigned })]
+				[id: 232] expression: Alias [name = FIRST_NAME, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 2, col_type: String })]
+				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned })]
+				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 15] relation: Projection
+[id: 964] relation: Projection
 	Children:
-		Child_id = 11
-	Output_id: 14
-		[id: 14] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 664
+	Output_id: 864
+		[id: 864] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 13] expression: Alias [name = id, child = Reference { parent: Some(NodeId { offset: 15, arena_type: Default }), targets: Some([0]), position: 0, col_type: Unsigned }]
+				[id: 532] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 19] relation: ScanSubQuery
+[id: 1264] relation: ScanSubQuery
 	Alias: t1
 	Children:
-		Child_id = 15
-	Output_id: 18
-		[id: 18] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 964
+	Output_id: 1164
+		[id: 1164] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 17] expression: Alias [name = id, child = Reference { parent: Some(NodeId { offset: 19, arena_type: Default }), targets: Some([0]), position: 0, col_type: Unsigned }]
+				[id: 632] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 12, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 31] relation: ScanRelation
+[id: 1964] relation: ScanRelation
 	Relation: hash_testing
 	[No children]
-	Output_id: 30
-		[id: 30] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
+	Output_id: 1864
+		[id: 1864] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 21] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 0, col_type: Integer }]
-				[id: 23] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 1, col_type: String }]
-				[id: 25] expression: Alias [name = product_units, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 2, col_type: Boolean }]
-				[id: 27] expression: Alias [name = sys_op, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 3, col_type: Unsigned }]
-				[id: 29] expression: Alias [name = bucket_id, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 4, col_type: Unsigned }]
+				[id: 732] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer })]
+				[id: 832] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 1, col_type: String })]
+				[id: 932] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean })]
+				[id: 1032] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned })]
+				[id: 1132] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 35] relation: Projection
+[id: 2264] relation: Projection
 	Children:
-		Child_id = 31
-	Output_id: 34
-		[id: 34] expression: Row [distribution = Some(Any)]
+		Child_id = 1964
+	Output_id: 2164
+		[id: 2164] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 33] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 35, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
+				[id: 1232] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 22, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 39] relation: ScanSubQuery
+[id: 2564] relation: ScanSubQuery
 	Alias: t2
 	Children:
-		Child_id = 35
-	Output_id: 38
-		[id: 38] expression: Row [distribution = Some(Any)]
+		Child_id = 2264
+	Output_id: 2464
+		[id: 2464] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 37] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 39, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
+				[id: 1332] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 25, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 61] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] })]
+[id: 0136] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] })]
 	Children:
-		Child_id = 39
-	Output_id: 60
-		[id: 60] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 2564
+	Output_id: 4064
+		[id: 4064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 59] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 61, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
+				[id: 1932] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 0, arena_type: Arena136 }), targets: Some([0]), position: 0, col_type: Integer })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 50] relation: InnerJoin
+[id: 3364] relation: InnerJoin
 	Condition:
-		[id: 57] expression: Bool [op: =]
+		[id: 1832] expression: Bool [op: =]
 			Left child
-			[id: 55] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+			[id: 3764] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 				List:
-					[id: 40] expression: Reference
+					[id: 2664] expression: Reference
 						Alias: id
 						Referenced table name (or alias): t1
-						Parent: Some(NodeId { offset: 50, arena_type: Default })
+						Parent: Some(NodeId { offset: 33, arena_type: Arena64 })
 						target_id: 0
 						Column type: unsigned
 			Right child
-			[id: 56] expression: Row [distribution = Some(Any)]
+			[id: 3864] expression: Row [distribution = Some(Any)]
 				List:
-					[id: 42] expression: Reference
+					[id: 2864] expression: Reference
 						Alias: identification_number
 						Referenced table name (or alias): t2
-						Parent: Some(NodeId { offset: 50, arena_type: Default })
+						Parent: Some(NodeId { offset: 33, arena_type: Arena64 })
 						target_id: 1
 						Column type: integer
 	Children:
-		Child_id = 19
-		Child_id = 61
-	Output_id: 49
-		[id: 49] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [1] }, Key { positions: [0] }}) })]
+		Child_id = 1264
+		Child_id = 0136
+	Output_id: 3264
+		[id: 3264] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [1] }, Key { positions: [0] }}) })]
 			List:
-				[id: 46] expression: Alias [name = id, child = Reference { parent: Some(NodeId { offset: 50, arena_type: Default }), targets: Some([0]), position: 0, col_type: Unsigned }]
-				[id: 48] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 50, arena_type: Default }), targets: Some([1]), position: 0, col_type: Integer }]
+				[id: 1532] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 33, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned })]
+				[id: 1632] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 33, arena_type: Arena64 }), targets: Some([1]), position: 0, col_type: Integer })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 54] relation: Projection
+[id: 3664] relation: Projection
 	Children:
-		Child_id = 50
-	Output_id: 53
-		[id: 53] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 3364
+	Output_id: 3564
+		[id: 3564] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 52] expression: Alias [name = id, child = Reference { parent: Some(NodeId { offset: 54, arena_type: Default }), targets: Some([0]), position: 0, col_type: Unsigned }]
+				[id: 1732] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 36, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned })]
 ---------------------------------------------
 "#);
 
@@ -178,56 +181,55 @@ fn simple_join_subtree() {
 
     // Taken from the expected arena output in the `simple_join` test.
     let inner_join_inner_child_id = NodeId {
-        offset: 61,
-        arena_type: ArenaType::Default,
+        offset: 0,
+        arena_type: ArenaType::Arena136,
     };
 
     let actual_arena_subtree = plan
         .formatted_arena_subtree(inner_join_inner_child_id)
         .unwrap();
-
     let mut expected_arena_subtree = String::new();
     expected_arena_subtree.push_str(
-    	r#"---------------------------------------------
-[id: 31] relation: ScanRelation
+        r#"---------------------------------------------
+[id: 1964] relation: ScanRelation
 	Relation: hash_testing
 	[No children]
-	Output_id: 30
-		[id: 30] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
+	Output_id: 1864
+		[id: 1864] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 21] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 0, col_type: Integer }]
-				[id: 23] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 1, col_type: String }]
-				[id: 25] expression: Alias [name = product_units, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 2, col_type: Boolean }]
-				[id: 27] expression: Alias [name = sys_op, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 3, col_type: Unsigned }]
-				[id: 29] expression: Alias [name = bucket_id, child = Reference { parent: Some(NodeId { offset: 31, arena_type: Default }), targets: None, position: 4, col_type: Unsigned }]
+				[id: 732] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer })]
+				[id: 832] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 1, col_type: String })]
+				[id: 932] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean })]
+				[id: 1032] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned })]
+				[id: 1132] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 19, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 35] relation: Projection
+[id: 2264] relation: Projection
 	Children:
-		Child_id = 31
-	Output_id: 34
-		[id: 34] expression: Row [distribution = Some(Any)]
+		Child_id = 1964
+	Output_id: 2164
+		[id: 2164] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 33] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 35, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
+				[id: 1232] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 22, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 39] relation: ScanSubQuery
+[id: 2564] relation: ScanSubQuery
 	Alias: t2
 	Children:
-		Child_id = 35
-	Output_id: 38
-		[id: 38] expression: Row [distribution = Some(Any)]
+		Child_id = 2264
+	Output_id: 2464
+		[id: 2464] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 37] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 39, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
+				[id: 1332] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 25, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 61] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] })]
+[id: 0136] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] })]
 	Children:
-		Child_id = 39
-	Output_id: 60
-		[id: 60] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 2564
+	Output_id: 4064
+		[id: 4064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 59] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 61, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
+				[id: 1932] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 0, arena_type: Arena136 }), targets: Some([0]), position: 0, col_type: Integer })]
 ---------------------------------------------
 "#
     );
@@ -244,79 +246,79 @@ fn simple_aggregation_with_group_by() {
     let mut expected_arena = String::new();
     expected_arena.push_str(
         r#"---------------------------------------------
-[id: 11] relation: ScanRelation
+[id: 664] relation: ScanRelation
 	Relation: hash_testing
 	[No children]
-	Output_id: 10
-		[id: 10] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
+	Output_id: 564
+		[id: 564] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 1] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 0, col_type: Integer }]
-				[id: 3] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 1, col_type: String }]
-				[id: 5] expression: Alias [name = product_units, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 2, col_type: Boolean }]
-				[id: 7] expression: Alias [name = sys_op, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 3, col_type: Unsigned }]
-				[id: 9] expression: Alias [name = bucket_id, child = Reference { parent: Some(NodeId { offset: 11, arena_type: Default }), targets: None, position: 4, col_type: Unsigned }]
+				[id: 032] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer })]
+				[id: 132] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 1, col_type: String })]
+				[id: 232] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean })]
+				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned })]
+				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 6, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 24] relation: GroupBy [is_final = false]
+[id: 1464] relation: GroupBy [is_final = false]
 	Gr_cols:
-		Gr_col: Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 1, col_type: String }
+		Gr_col: Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String })
 	Children:
-		Child_id = 11
-	Output_id: 23
-		[id: 23] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
+		Child_id = 664
+	Output_id: 1364
+		[id: 1364] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 14] expression: Alias [name = identification_number, child = Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 0, col_type: Integer }]
-				[id: 16] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 1, col_type: String }]
-				[id: 18] expression: Alias [name = product_units, child = Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 2, col_type: Boolean }]
-				[id: 20] expression: Alias [name = sys_op, child = Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 3, col_type: Unsigned }]
-				[id: 22] expression: Alias [name = bucket_id, child = Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 4, col_type: Unsigned }]
+				[id: 532] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer })]
+				[id: 632] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String })]
+				[id: 732] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 2, col_type: Boolean })]
+				[id: 832] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 3, col_type: Unsigned })]
+				[id: 932] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 4, col_type: Unsigned })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 31] relation: Projection
+[id: 1964] relation: Projection
 	Children:
-		Child_id = 24
-	Output_id: 30
-		[id: 30] expression: Row [distribution = Some(Any)]
+		Child_id = 1464
+	Output_id: 1864
+		[id: 1864] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 29] expression: Alias [name = column_12, child = Reference { parent: Some(NodeId { offset: 24, arena_type: Default }), targets: Some([0]), position: 1, col_type: String }]
+				[id: 1132] expression: Alias [name = column_764, child = Reference(Reference { parent: Some(NodeId { offset: 14, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 35] relation: ScanSubQuery
+[id: 2264] relation: ScanSubQuery
 	Children:
-		Child_id = 31
-	Output_id: 34
-		[id: 34] expression: Row [distribution = Some(Any)]
+		Child_id = 1964
+	Output_id: 2164
+		[id: 2164] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 33] expression: Alias [name = column_12, child = Reference { parent: Some(NodeId { offset: 35, arena_type: Default }), targets: Some([0]), position: 0, col_type: String }]
+				[id: 1232] expression: Alias [name = column_764, child = Reference(Reference { parent: Some(NodeId { offset: 22, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 45] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] })]
+[id: 0136] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] })]
 	Children:
-		Child_id = 35
-	Output_id: 44
-		[id: 44] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 2264
+	Output_id: 2964
+		[id: 2964] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 43] expression: Alias [name = column_12, child = Reference { parent: Some(NodeId { offset: 45, arena_type: Default }), targets: Some([0]), position: 0, col_type: String }]
+				[id: 1432] expression: Alias [name = column_764, child = Reference(Reference { parent: Some(NodeId { offset: 0, arena_type: Arena136 }), targets: Some([0]), position: 0, col_type: String })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 40] relation: GroupBy [is_final = true]
+[id: 2664] relation: GroupBy [is_final = true]
 	Gr_cols:
-		Gr_col: Reference { parent: Some(NodeId { offset: 40, arena_type: Default }), targets: Some([0]), position: 0, col_type: String }
+		Gr_col: Reference(Reference { parent: Some(NodeId { offset: 26, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String })
 	Children:
-		Child_id = 45
-	Output_id: 39
-		[id: 39] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 0136
+	Output_id: 2564
+		[id: 2564] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 38] expression: Alias [name = column_12, child = Reference { parent: Some(NodeId { offset: 40, arena_type: Default }), targets: Some([0]), position: 0, col_type: String }]
+				[id: 1332] expression: Alias [name = column_764, child = Reference(Reference { parent: Some(NodeId { offset: 26, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String })]
 ---------------------------------------------
 ---------------------------------------------
-[id: 28] relation: Projection
+[id: 1764] relation: Projection
 	Children:
-		Child_id = 40
-	Output_id: 27
-		[id: 27] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
+		Child_id = 2664
+	Output_id: 1664
+		[id: 1664] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 26] expression: Alias [name = product_code, child = Reference { parent: Some(NodeId { offset: 28, arena_type: Default }), targets: Some([0]), position: 0, col_type: String }]
+				[id: 1032] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 17, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String })]
 ---------------------------------------------
 "#);
 
diff --git a/sbroad-core/src/ir/node.rs b/sbroad-core/src/ir/node.rs
new file mode 100644
index 000000000..fd93c2aeb
--- /dev/null
+++ b/sbroad-core/src/ir/node.rs
@@ -0,0 +1,1310 @@
+use std::{collections::HashMap, fmt::Display};
+
+use acl::{Acl, AclOwned, MutAcl};
+use block::{Block, BlockOwned, MutBlock};
+use ddl::{Ddl, DdlOwned, MutDdl};
+use expression::{ExprOwned, Expression, MutExpression};
+use relational::{MutRelational, RelOwned, Relational};
+use serde::{Deserialize, Serialize};
+use smol_str::SmolStr;
+use tarantool::{
+    decimal::Decimal,
+    index::{IndexType, RtreeIndexDistanceType},
+    space::SpaceEngineType,
+};
+
+use crate::{
+    errors::SbroadError,
+    ir::{
+        acl::{AlterOption, GrantRevokeType},
+        ddl::{ColumnDef, Language, ParamDef, SetParamScopeType, SetParamValue},
+        distribution::Distribution,
+        helpers::RepeatableState,
+        relation::Type,
+        transformation::redistribution::{ColumnPosition, MotionPolicy, Program},
+        value::Value,
+    },
+};
+
+use super::{
+    ddl::AlterSystemType,
+    expression::{cast, FunctionFeature, TrimKind},
+    operator::{self, ConflictStrategy, JoinKind, OrderByElement, UpdateStrategy},
+};
+
+pub mod acl;
+pub mod block;
+pub mod ddl;
+pub mod expression;
+pub mod relational;
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Hash, Copy)]
+pub enum ArenaType {
+    Arena32,
+    Arena64,
+    Arena96,
+    Arena136,
+    Arena224,
+}
+
+impl Display for ArenaType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ArenaType::Arena32 => {
+                write!(f, "32")
+            }
+            ArenaType::Arena64 => {
+                write!(f, "64")
+            }
+            ArenaType::Arena96 => {
+                write!(f, "96")
+            }
+            ArenaType::Arena136 => {
+                write!(f, "136")
+            }
+            ArenaType::Arena224 => {
+                write!(f, "224")
+            }
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash, Copy)]
+pub struct NodeId {
+    pub offset: u32,
+    pub arena_type: ArenaType,
+}
+
+impl Display for NodeId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}{}", self.offset, self.arena_type)
+    }
+}
+
+impl Default for NodeId {
+    fn default() -> Self {
+        NodeId {
+            offset: 0,
+            arena_type: ArenaType::Arena32,
+        }
+    }
+}
+
+/// Expression name.
+///
+/// Example: `42 as a`.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Alias {
+    /// Alias name.
+    pub name: SmolStr,
+    /// Child expression node index in the plan node arena.
+    pub child: NodeId,
+}
+
+impl From<Alias> for SizeNode {
+    fn from(value: Alias) -> Self {
+        Self::Node32(Node32::Alias(value))
+    }
+}
+
+/// Binary expression returning boolean result.
+///
+/// Example: `a > 42`, `b in (select c from ...)`.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct BoolExpr {
+    /// Left branch expression node index in the plan node arena.
+    pub left: NodeId,
+    /// Boolean operator.
+    pub op: operator::Bool,
+    /// Right branch expression node index in the plan node arena.
+    pub right: NodeId,
+}
+
+impl From<BoolExpr> for SizeNode {
+    fn from(value: BoolExpr) -> Self {
+        Self::Node32(Node32::Bool(value))
+    }
+}
+
+/// Binary expression returning row result.
+///
+/// Example: `a + b > 42`, `a + b < c + 1`, `1 + 2 != 2 * 2`.
+///
+/// TODO: always cover children with parentheses (in `to_sql`).
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct ArithmeticExpr {
+    /// Left branch expression node index in the plan node arena.
+    pub left: NodeId,
+    /// Arithmetic operator.
+    pub op: operator::Arithmetic,
+    /// Right branch expression node index in the plan node arena.
+    pub right: NodeId,
+}
+
+impl From<ArithmeticExpr> for SizeNode {
+    fn from(value: ArithmeticExpr) -> Self {
+        Self::Node32(Node32::Arithmetic(value))
+    }
+}
+
+/// Type cast expression.
+///
+/// Example: `cast(a as text)`.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Cast {
+    /// Target expression that must be casted to another type.
+    pub child: NodeId,
+    /// Cast type.
+    pub to: cast::Type,
+}
+
+impl From<Cast> for SizeNode {
+    fn from(value: Cast) -> Self {
+        Self::Node32(Node32::Cast(value))
+    }
+}
+
+/// String concatenation expression.
+///
+/// Example: `a || 'hello'`.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Concat {
+    /// Left expression node id.
+    pub left: NodeId,
+    /// Right expression node id.
+    pub right: NodeId,
+}
+
+impl From<Concat> for SizeNode {
+    fn from(value: Concat) -> Self {
+        Self::Node32(Node32::Concat(value))
+    }
+}
+
+/// Constant expressions.
+///
+/// Example: `42`.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Constant {
+    /// Contained value (boolean, number, string or null)
+    pub value: Value,
+}
+
+impl From<Constant> for SizeNode {
+    fn from(value: Constant) -> Self {
+        Self::Node64(Node64::Constant(value))
+    }
+}
+
+/// Reference to the position in the incoming tuple(s).
+/// Uses a relative pointer as a coordinate system:
+/// - relational node (containing this reference)
+/// - target(s) in the relational nodes list of children
+/// - column position in the child(ren) output tuple
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Reference {
+    /// Relational node ID that contains current reference.
+    pub parent: Option<NodeId>,
+    /// Targets in the relational node children list.
+    /// - Leaf nodes (relation scans): None.
+    /// - Union nodes: two elements (left and right).
+    /// - Other: single element.
+    pub targets: Option<Vec<usize>>,
+    /// Expression position in the input tuple (i.e. `Alias` column).
+    pub position: usize,
+    /// Referred column type in the input tuple.
+    pub col_type: Type,
+}
+
+impl From<Reference> for SizeNode {
+    fn from(value: Reference) -> Self {
+        Self::Node64(Node64::Reference(value))
+    }
+}
+
+/// Top of the tuple tree.
+///
+/// If the current tuple is the output for some relational operator, it should
+/// consist of the list of aliases. Otherwise (rows in selection filter
+/// or in join condition) we don't require aliases in the list.
+///
+///
+///  Example: (a, b, 1).
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Row {
+    /// A list of the alias expression node indexes in the plan node arena.
+    pub list: Vec<NodeId>,
+    /// Resulting data distribution of the tuple. Should be filled as a part
+    /// of the last "add Motion" transformation.
+    pub distribution: Option<Distribution>,
+}
+
+impl From<Row> for SizeNode {
+    fn from(value: Row) -> Self {
+        Self::Node64(Node64::Row(value))
+    }
+}
+
+/// Stable function cannot modify the database and
+/// is guaranteed to return the same results given
+/// the same arguments for all rows within a single
+/// statement.
+///
+/// Example: `bucket_id("1")` (the number of buckets can be
+/// changed only after restarting the cluster).
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct StableFunction {
+    /// Function name.
+    pub name: SmolStr,
+    /// Function arguments.
+    pub children: Vec<NodeId>,
+    /// Optional function feature.
+    pub feature: Option<FunctionFeature>,
+    /// Function return type.
+    pub func_type: Type,
+    /// Whether function is provided by tarantool,
+    /// when referencing these funcs from local
+    /// sql we must not use quotes.
+    /// Examples: aggregates, substr
+    pub is_system: bool,
+}
+
+impl From<StableFunction> for SizeNode {
+    fn from(value: StableFunction) -> Self {
+        Self::Node96(Node96::StableFunction(value))
+    }
+}
+
+/// Trim expression.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Trim {
+    /// Trim kind.
+    pub kind: Option<TrimKind>,
+    /// Trim string pattern to remove (it can be an expression).
+    pub pattern: Option<NodeId>,
+    /// Target expression to trim.
+    pub target: NodeId,
+}
+
+impl From<Trim> for SizeNode {
+    fn from(value: Trim) -> Self {
+        Self::Node32(Node32::Trim(value))
+    }
+}
+
+/// Unary expression returning boolean result.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct UnaryExpr {
+    /// Unary operator.
+    pub op: operator::Unary,
+    /// Child expression node index in the plan node arena.
+    pub child: NodeId,
+}
+
+impl From<UnaryExpr> for SizeNode {
+    fn from(value: UnaryExpr) -> Self {
+        Self::Node32(Node32::Unary(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct ExprInParentheses {
+    pub child: NodeId,
+}
+
+impl From<ExprInParentheses> for SizeNode {
+    fn from(value: ExprInParentheses) -> Self {
+        Self::Node32(Node32::ExprInParentheses(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Case {
+    pub search_expr: Option<NodeId>,
+    pub when_blocks: Vec<(NodeId, NodeId)>,
+    pub else_expr: Option<NodeId>,
+}
+
+impl From<Case> for SizeNode {
+    fn from(value: Case) -> Self {
+        Self::Node64(Node64::Case(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Parameter {
+    pub param_type: Option<Type>,
+}
+
+impl From<Parameter> for SizeNode {
+    fn from(value: Parameter) -> Self {
+        Self::Node64(Node64::Parameter(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct CountAsterisk {}
+
+impl From<CountAsterisk> for SizeNode {
+    fn from(value: CountAsterisk) -> Self {
+        Self::Node32(Node32::CountAsterisk(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct ScanCte {
+    /// CTE's name.
+    pub alias: SmolStr,
+    /// Contains exactly one single element (projection node index).
+    pub child: NodeId,
+    /// An output tuple with aliases.
+    pub output: NodeId,
+}
+
+impl From<ScanCte> for SizeNode {
+    fn from(value: ScanCte) -> Self {
+        Self::Node64(Node64::ScanCte(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Except {
+    /// Left child id
+    pub left: NodeId,
+    /// Right child id
+    pub right: NodeId,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+}
+
+impl From<Except> for SizeNode {
+    fn from(value: Except) -> Self {
+        Self::Node32(Node32::Except(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Delete {
+    /// Relation name.
+    pub relation: SmolStr,
+    /// Contains exactly one single element.
+    pub children: Vec<NodeId>,
+    /// The output tuple (reserved for `delete returning`).
+    pub output: NodeId,
+}
+
+impl From<Delete> for SizeNode {
+    fn from(value: Delete) -> Self {
+        Self::Node64(Node64::Delete(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Insert {
+    /// Relation name.
+    pub relation: SmolStr,
+    /// Target column positions for data insertion from
+    /// the child's tuple.
+    pub columns: Vec<usize>,
+    /// Contains exactly one single element.
+    pub children: Vec<NodeId>,
+    /// The output tuple (reserved for `insert returning`).
+    pub output: NodeId,
+    /// What to do in case there is a conflict during insert on storage
+    pub conflict_strategy: ConflictStrategy,
+}
+
+impl From<Insert> for SizeNode {
+    fn from(value: Insert) -> Self {
+        Self::Node96(Node96::Insert(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Intersect {
+    pub left: NodeId,
+    pub right: NodeId,
+    // id of the output tuple
+    pub output: NodeId,
+}
+
+impl From<Intersect> for SizeNode {
+    fn from(value: Intersect) -> Self {
+        Self::Node32(Node32::Intersect(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Update {
+    /// Relation name.
+    pub relation: SmolStr,
+    /// Children ids. Update has exactly one child.
+    pub children: Vec<NodeId>,
+    /// Maps position of column being updated in table to corresponding position
+    /// in `Projection` below `Update`.
+    ///
+    /// For sharded `Update`, it will contain every table column except `bucket_id`
+    /// column. For local `Update` it will contain only update table columns.
+    pub update_columns_map: HashMap<ColumnPosition, ColumnPosition, RepeatableState>,
+    /// How this update must be executed.
+    pub strategy: UpdateStrategy,
+    /// Positions of primary columns in `Projection`
+    /// below `Update`.
+    pub pk_positions: Vec<ColumnPosition>,
+    /// Output id.
+    pub output: NodeId,
+}
+
+impl From<Update> for SizeNode {
+    fn from(value: Update) -> Self {
+        Self::Node136(Node136::Update(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Join {
+    /// Contains at least two elements: left and right node indexes
+    /// from the plan node arena. Every element other than those
+    /// two should be treated as a `SubQuery` node.
+    pub children: Vec<NodeId>,
+    /// Left and right tuple comparison condition.
+    /// In fact it is an expression tree top index from the plan node arena.
+    pub condition: NodeId,
+    /// Outputs tuple node index from the plan node arena.
+    pub output: NodeId,
+    /// inner or left
+    pub kind: JoinKind,
+}
+
+impl From<Join> for SizeNode {
+    fn from(value: Join) -> Self {
+        Self::Node64(Node64::Join(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Limit {
+    /// Output tuple.
+    pub output: NodeId,
+    // The limit value constant that comes after LIMIT keyword.
+    pub limit: u64,
+    /// Select statement that is being limited.
+    /// Note that it can be a complex statement, like SELECT .. UNION ALL SELECT .. LIMIT 100,
+    /// in that case limit is applied to the result of union.
+    pub child: NodeId,
+}
+
+impl From<Limit> for SizeNode {
+    fn from(value: Limit) -> Self {
+        Self::Node32(Node32::Limit(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Motion {
+    // Scan name.
+    pub alias: Option<SmolStr>,
+    /// Contains exactly one single element: child node index
+    /// from the plan node arena.
+    pub children: Vec<NodeId>,
+    /// Motion policy - the amount of data to be moved.
+    pub policy: MotionPolicy,
+    /// A sequence of opcodes that transform the data.
+    pub program: Program,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+    /// A helper field indicating whether first element of
+    /// `children` vec is a `Relational::SubQuery`.
+    /// We need it on the stage of translating Plan to SQL, because
+    /// by that moment we've already erased `children` information using
+    /// `unlink_motion_subtree` function.
+    pub is_child_subquery: bool,
+}
+
+impl From<Motion> for SizeNode {
+    fn from(value: Motion) -> Self {
+        Self::Node136(Node136::Motion(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Projection {
+    /// Contains at least one single element: child node index
+    /// from the plan node arena. Every element other than the
+    /// first one should be treated as a `SubQuery` node from
+    /// the output tree.
+    pub children: Vec<NodeId>,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+    /// Wheter the select was marked with `distinct` keyword
+    pub is_distinct: bool,
+}
+
+impl From<Projection> for SizeNode {
+    fn from(value: Projection) -> Self {
+        Self::Node64(Node64::Projection(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct ScanRelation {
+    // Scan name.
+    pub alias: Option<SmolStr>,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+    /// Relation name.
+    pub relation: SmolStr,
+}
+
+impl From<ScanRelation> for SizeNode {
+    fn from(value: ScanRelation) -> Self {
+        Self::Node64(Node64::ScanRelation(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct ScanSubQuery {
+    /// SubQuery name.
+    pub alias: Option<SmolStr>,
+    /// Contains exactly one single element: child node index
+    /// from the plan node arena.
+    pub children: Vec<NodeId>,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+}
+
+impl From<ScanSubQuery> for SizeNode {
+    fn from(value: ScanSubQuery) -> Self {
+        Self::Node64(Node64::ScanSubQuery(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Selection {
+    /// Contains at least one single element: child node index
+    /// from the plan node arena. Every element other than the
+    /// first one should be treated as a `SubQuery` node from
+    /// the filter tree.
+    pub children: Vec<NodeId>,
+    /// Filters expression node index in the plan node arena.
+    pub filter: NodeId,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+}
+
+impl From<Selection> for SizeNode {
+    fn from(value: Selection) -> Self {
+        Self::Node64(Node64::Selection(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct GroupBy {
+    /// The first child is a relational operator before group by
+    pub children: Vec<NodeId>,
+    pub gr_cols: Vec<NodeId>,
+    pub output: NodeId,
+    pub is_final: bool,
+}
+
+impl From<GroupBy> for SizeNode {
+    fn from(value: GroupBy) -> Self {
+        Self::Node64(Node64::GroupBy(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Having {
+    pub children: Vec<NodeId>,
+    pub output: NodeId,
+    pub filter: NodeId,
+}
+
+impl From<Having> for SizeNode {
+    fn from(value: Having) -> Self {
+        Self::Node64(Node64::Having(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct OrderBy {
+    pub child: NodeId,
+    pub output: NodeId,
+    pub order_by_elements: Vec<OrderByElement>,
+}
+
+impl From<OrderBy> for SizeNode {
+    fn from(value: OrderBy) -> Self {
+        Self::Node64(Node64::OrderBy(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct UnionAll {
+    /// Left child id
+    pub left: NodeId,
+    /// Right child id
+    pub right: NodeId,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+}
+
+impl From<UnionAll> for SizeNode {
+    fn from(value: UnionAll) -> Self {
+        Self::Node32(Node32::UnionAll(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Values {
+    /// Output tuple.
+    pub output: NodeId,
+    /// Non-empty list of value rows.
+    pub children: Vec<NodeId>,
+}
+
+impl From<Values> for SizeNode {
+    fn from(value: Values) -> Self {
+        Self::Node32(Node32::Values(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct ValuesRow {
+    /// Output tuple of aliases.
+    pub output: NodeId,
+    /// The data tuple.
+    pub data: NodeId,
+    /// A list of children is required for the rows containing
+    /// sub-queries. For example, the row `(1, (select a from t))`
+    /// requires `children` to keep projection node. If the row
+    /// contains only constants (i.e. `(1, 2)`), then `children`
+    /// should be empty.
+    pub children: Vec<NodeId>,
+}
+
+impl From<ValuesRow> for SizeNode {
+    fn from(value: ValuesRow) -> Self {
+        Self::Node64(Node64::ValuesRow(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct DropRole {
+    pub name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<DropRole> for SizeNode {
+    fn from(value: DropRole) -> Self {
+        Self::Node64(Node64::DropRole(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct DropUser {
+    pub name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<DropUser> for SizeNode {
+    fn from(value: DropUser) -> Self {
+        Self::Node64(Node64::DropUser(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct CreateRole {
+    pub name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<CreateRole> for SizeNode {
+    fn from(value: CreateRole) -> Self {
+        Self::Node64(Node64::CreateRole(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct CreateUser {
+    pub name: SmolStr,
+    pub password: SmolStr,
+    pub auth_method: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<CreateUser> for SizeNode {
+    fn from(value: CreateUser) -> Self {
+        Self::Node136(Node136::CreateUser(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct AlterUser {
+    pub name: SmolStr,
+    pub alter_option: AlterOption,
+    pub timeout: Decimal,
+}
+
+impl From<AlterUser> for SizeNode {
+    fn from(value: AlterUser) -> Self {
+        Self::Node136(Node136::AlterUser(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct GrantPrivilege {
+    pub grant_type: GrantRevokeType,
+    pub grantee_name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<GrantPrivilege> for SizeNode {
+    fn from(value: GrantPrivilege) -> Self {
+        Self::Node136(Node136::GrantPrivilege(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct RevokePrivilege {
+    pub revoke_type: GrantRevokeType,
+    pub grantee_name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<RevokePrivilege> for SizeNode {
+    fn from(value: RevokePrivilege) -> Self {
+        Self::Node136(Node136::RevokePrivilege(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct CreateTable {
+    pub name: SmolStr,
+    pub format: Vec<ColumnDef>,
+    pub primary_key: Vec<SmolStr>,
+    /// If `None`, create global table.
+    pub sharding_key: Option<Vec<SmolStr>>,
+    /// Vinyl is supported only for sharded tables.
+    pub engine_type: SpaceEngineType,
+    pub timeout: Decimal,
+    /// Shows which tier the sharded table belongs to.
+    /// Field has value, only if it was specified in [ON TIER] part of CREATE TABLE statement.
+    /// Field is None, if:
+    /// 1) Global table.
+    /// 2) Sharded table without [ON TIER] part. In this case picodata will use default tier.
+    pub tier: Option<SmolStr>,
+}
+
+impl From<CreateTable> for SizeNode {
+    fn from(value: CreateTable) -> Self {
+        Self::Node224(Node224::CreateTable(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct DropTable {
+    pub name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<DropTable> for SizeNode {
+    fn from(value: DropTable) -> Self {
+        Self::Node64(Node64::DropTable(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct CreateProc {
+    pub name: SmolStr,
+    pub params: Vec<ParamDef>,
+    pub body: SmolStr,
+    pub language: Language,
+    pub timeout: Decimal,
+}
+
+impl From<CreateProc> for SizeNode {
+    fn from(value: CreateProc) -> Self {
+        Self::Node136(Node136::CreateProc(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct DropProc {
+    pub name: SmolStr,
+    pub params: Option<Vec<ParamDef>>,
+    pub timeout: Decimal,
+}
+
+impl From<DropProc> for SizeNode {
+    fn from(value: DropProc) -> Self {
+        Self::Node96(Node96::DropProc(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct RenameRoutine {
+    pub old_name: SmolStr,
+    pub new_name: SmolStr,
+    pub params: Option<Vec<ParamDef>>,
+    pub timeout: Decimal,
+}
+
+impl From<RenameRoutine> for SizeNode {
+    fn from(value: RenameRoutine) -> Self {
+        Self::Node136(Node136::RenameRoutine(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct CreateIndex {
+    pub name: SmolStr,
+    pub table_name: SmolStr,
+    pub columns: Vec<SmolStr>,
+    pub unique: bool,
+    pub index_type: IndexType,
+    pub bloom_fpr: Option<Decimal>,
+    pub page_size: Option<u32>,
+    pub range_size: Option<u32>,
+    pub run_count_per_level: Option<u32>,
+    pub run_size_ratio: Option<Decimal>,
+    pub dimension: Option<u32>,
+    pub distance: Option<RtreeIndexDistanceType>,
+    pub hint: Option<bool>,
+    pub timeout: Decimal,
+}
+
+impl From<CreateIndex> for SizeNode {
+    fn from(value: CreateIndex) -> Self {
+        Self::Node224(Node224::CreateIndex(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct DropIndex {
+    pub name: SmolStr,
+    pub timeout: Decimal,
+}
+
+impl From<DropIndex> for SizeNode {
+    fn from(value: DropIndex) -> Self {
+        Self::Node64(Node64::DropIndex(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct SetParam {
+    pub scope_type: SetParamScopeType,
+    pub param_value: SetParamValue,
+    pub timeout: Decimal,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct AlterSystem {
+    pub ty: AlterSystemType,
+    /// In case of None, ALTER is supposed
+    /// to be executed on all tiers.
+    pub tier_name: Option<SmolStr>,
+    pub timeout: Decimal,
+}
+
+impl From<AlterSystem> for SizeNode {
+    fn from(value: AlterSystem) -> Self {
+        Self::Node136(Node136::AlterSystem(value))
+    }
+}
+
+impl From<SetParam> for SizeNode {
+    fn from(value: SetParam) -> Self {
+        Self::Node64(Node64::SetParam(value))
+    }
+}
+
+// TODO: Fill with actual values.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct SetTransaction {
+    pub timeout: Decimal,
+}
+
+impl From<SetTransaction> for SizeNode {
+    fn from(value: SetTransaction) -> Self {
+        Self::Node64(Node64::SetTransaction(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub struct Invalid;
+
+impl From<Invalid> for SizeNode {
+    fn from(value: Invalid) -> Self {
+        Self::Node32(Node32::Invalid(value))
+    }
+}
+
+/// Procedure body.
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Procedure {
+    /// The name of the procedure.
+    pub name: SmolStr,
+    /// Passed values to the procedure.
+    pub values: Vec<NodeId>,
+}
+
+impl From<Procedure> for SizeNode {
+    fn from(value: Procedure) -> Self {
+        Self::Node64(Node64::Procedure(value))
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Union {
+    /// Left child id
+    pub left: NodeId,
+    /// Right child id
+    pub right: NodeId,
+    /// Outputs tuple node index in the plan node arena.
+    pub output: NodeId,
+}
+
+impl From<Union> for SizeNode {
+    fn from(value: Union) -> Self {
+        Self::Node32(Node32::Union(value))
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum Node32 {
+    Invalid(Invalid),
+    Union(Union),
+    CountAsterisk(CountAsterisk),
+    ExprInParentheses(ExprInParentheses),
+    Unary(UnaryExpr),
+    Concat(Concat),
+    Bool(BoolExpr),
+    Limit(Limit),
+    Arithmetic(ArithmeticExpr),
+    Trim(Trim),
+    Cast(Cast),
+    Alias(Alias),
+    Except(Except),
+    Intersect(Intersect),
+    UnionAll(UnionAll),
+    Values(Values),
+}
+
+const _: () = {
+    assert!(std::mem::size_of::<Node32>() == 40);
+    assert!(std::mem::size_of::<Node64>() == 72);
+    assert!(std::mem::size_of::<Node96>() == 96);
+    assert!(std::mem::size_of::<Node136>() == 136);
+    assert!(std::mem::size_of::<Node224>() == 224);
+};
+impl Node32 {
+    #[must_use]
+    pub fn into_common_node(self) -> NodeOwned {
+        match self {
+            Node32::Alias(alias) => NodeOwned::Expression(ExprOwned::Alias(alias)),
+            Node32::Arithmetic(arithm) => NodeOwned::Expression(ExprOwned::Arithmetic(arithm)),
+            Node32::Bool(bool) => NodeOwned::Expression(ExprOwned::Bool(bool)),
+            Node32::Limit(limit) => NodeOwned::Relational(RelOwned::Limit(limit)),
+            Node32::Cast(cast) => NodeOwned::Expression(ExprOwned::Cast(cast)),
+            Node32::Concat(concat) => NodeOwned::Expression(ExprOwned::Concat(concat)),
+            Node32::CountAsterisk(count) => NodeOwned::Expression(ExprOwned::CountAsterisk(count)),
+            Node32::Except(except) => NodeOwned::Relational(RelOwned::Except(except)),
+            Node32::ExprInParentheses(expr_in_par) => {
+                NodeOwned::Expression(ExprOwned::ExprInParentheses(expr_in_par))
+            }
+            Node32::Intersect(intersect) => NodeOwned::Relational(RelOwned::Intersect(intersect)),
+            Node32::Invalid(inv) => NodeOwned::Invalid(inv),
+            Node32::Trim(trim) => NodeOwned::Expression(ExprOwned::Trim(trim)),
+            Node32::Unary(unary) => NodeOwned::Expression(ExprOwned::Unary(unary)),
+            Node32::Union(un) => NodeOwned::Relational(RelOwned::Union(un)),
+            Node32::UnionAll(union_all) => NodeOwned::Relational(RelOwned::UnionAll(union_all)),
+            Node32::Values(values) => NodeOwned::Relational(RelOwned::Values(values)),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum Node64 {
+    ScanCte(ScanCte),
+    Case(Case),
+    Parameter(Parameter),
+    Constant(Constant),
+    Projection(Projection),
+    Selection(Selection),
+    Having(Having),
+    ValuesRow(ValuesRow),
+    OrderBy(OrderBy),
+    Procedure(Procedure),
+    Join(Join),
+    Row(Row), // move to Node64
+    Reference(Reference),
+    Delete(Delete),
+    ScanRelation(ScanRelation),
+    ScanSubQuery(ScanSubQuery),
+    DropRole(DropRole),
+    DropUser(DropUser),
+    CreateRole(CreateRole),
+    DropTable(DropTable),
+    DropIndex(DropIndex),
+    GroupBy(GroupBy),
+    SetParam(SetParam),
+    SetTransaction(SetTransaction),
+}
+
+impl Node64 {
+    /// # Errors
+    pub fn into_expr_node(&self) -> Result<Expression<'_>, SbroadError> {
+        match self {
+            Node64::Case(case) => Ok(Expression::Case(case)),
+            Node64::Constant(constant) => Ok(Expression::Constant(constant)),
+            _ => Err(SbroadError::Invalid(crate::errors::Entity::Node, None)),
+        }
+    }
+
+    #[must_use]
+    pub fn into_common_node(self) -> NodeOwned {
+        match self {
+            Node64::Case(case) => NodeOwned::Expression(ExprOwned::Case(case)),
+            Node64::Constant(constant) => NodeOwned::Expression(ExprOwned::Constant(constant)),
+            Node64::CreateRole(create_role) => NodeOwned::Acl(AclOwned::CreateRole(create_role)),
+            Node64::Delete(delete) => NodeOwned::Relational(RelOwned::Delete(delete)),
+            Node64::DropIndex(drop_index) => NodeOwned::Ddl(DdlOwned::DropIndex(drop_index)),
+            Node64::DropRole(drop_role) => NodeOwned::Acl(AclOwned::DropRole(drop_role)),
+            Node64::DropTable(drop_table) => NodeOwned::Ddl(DdlOwned::DropTable(drop_table)),
+            Node64::DropUser(drop_user) => NodeOwned::Acl(AclOwned::DropUser(drop_user)),
+            Node64::GroupBy(group_by) => NodeOwned::Relational(RelOwned::GroupBy(group_by)),
+            Node64::Having(having) => NodeOwned::Relational(RelOwned::Having(having)),
+            Node64::Join(join) => NodeOwned::Relational(RelOwned::Join(join)),
+            Node64::OrderBy(order_by) => NodeOwned::Relational(RelOwned::OrderBy(order_by)),
+            Node64::Parameter(param) => NodeOwned::Parameter(param),
+            Node64::Row(row) => NodeOwned::Expression(ExprOwned::Row(row)),
+            Node64::Procedure(proc) => NodeOwned::Block(BlockOwned::Procedure(proc)),
+            Node64::Projection(proj) => NodeOwned::Relational(RelOwned::Projection(proj)),
+            Node64::Reference(reference) => NodeOwned::Expression(ExprOwned::Reference(reference)),
+            Node64::ScanCte(scan_cte) => NodeOwned::Relational(RelOwned::ScanCte(scan_cte)),
+            Node64::ScanRelation(scan_rel) => {
+                NodeOwned::Relational(RelOwned::ScanRelation(scan_rel))
+            }
+            Node64::ScanSubQuery(scan_squery) => {
+                NodeOwned::Relational(RelOwned::ScanSubQuery(scan_squery))
+            }
+            Node64::Selection(sel) => NodeOwned::Relational(RelOwned::Selection(sel)),
+            Node64::SetParam(set_param) => NodeOwned::Ddl(DdlOwned::SetParam(set_param)),
+            Node64::SetTransaction(set_trans) => {
+                NodeOwned::Ddl(DdlOwned::SetTransaction(set_trans))
+            }
+            Node64::ValuesRow(values_row) => NodeOwned::Relational(RelOwned::ValuesRow(values_row)),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum Node96 {
+    Invalid(Invalid),
+    StableFunction(StableFunction),
+    DropProc(DropProc),
+    Insert(Insert),
+}
+
+impl Node96 {
+    #[must_use]
+    pub fn into_common_node(self) -> NodeOwned {
+        match self {
+            Node96::DropProc(drop_proc) => NodeOwned::Ddl(DdlOwned::DropProc(drop_proc)),
+            Node96::Insert(insert) => NodeOwned::Relational(RelOwned::Insert(insert)),
+            Node96::Invalid(inv) => NodeOwned::Invalid(inv),
+            Node96::StableFunction(stable_func) => {
+                NodeOwned::Expression(ExprOwned::StableFunction(stable_func))
+            }
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum Node136 {
+    Invalid(Invalid),
+    CreateUser(CreateUser),
+    AlterUser(AlterUser),
+    AlterSystem(AlterSystem),
+    CreateProc(CreateProc),
+    RenameRoutine(RenameRoutine),
+    Motion(Motion),
+    GrantPrivilege(GrantPrivilege),
+    RevokePrivilege(RevokePrivilege),
+    Update(Update),
+}
+
+impl Node136 {
+    #[must_use]
+    pub fn into_common_node(self) -> NodeOwned {
+        match self {
+            Node136::AlterUser(alter_user) => NodeOwned::Acl(AclOwned::AlterUser(alter_user)),
+            Node136::AlterSystem(alter_system) => {
+                NodeOwned::Ddl(DdlOwned::AlterSystem(alter_system))
+            }
+            Node136::CreateProc(create_proc) => NodeOwned::Ddl(DdlOwned::CreateProc(create_proc)),
+            Node136::GrantPrivilege(grant_privelege) => {
+                NodeOwned::Acl(AclOwned::GrantPrivilege(grant_privelege))
+            }
+            Node136::CreateUser(create_user) => NodeOwned::Acl(AclOwned::CreateUser(create_user)),
+            Node136::RevokePrivilege(revoke_privelege) => {
+                NodeOwned::Acl(AclOwned::RevokePrivilege(revoke_privelege))
+            }
+            Node136::Invalid(inv) => NodeOwned::Invalid(inv),
+            Node136::Motion(motion) => NodeOwned::Relational(RelOwned::Motion(motion)),
+            Node136::Update(update) => NodeOwned::Relational(RelOwned::Update(update)),
+            Node136::RenameRoutine(rename_routine) => {
+                NodeOwned::Ddl(DdlOwned::RenameRoutine(rename_routine))
+            }
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions, clippy::large_enum_variant)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum Node224 {
+    Invalid(Invalid),
+    CreateTable(CreateTable),
+    CreateIndex(CreateIndex),
+}
+
+impl Node224 {
+    #[must_use]
+    pub fn into_common_node(self) -> NodeOwned {
+        match self {
+            Node224::CreateTable(create_table) => {
+                NodeOwned::Ddl(DdlOwned::CreateTable(create_table))
+            }
+            Node224::CreateIndex(create_index) => {
+                NodeOwned::Ddl(DdlOwned::CreateIndex(create_index))
+            }
+            Node224::Invalid(inv) => NodeOwned::Invalid(inv),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub enum SizeNode {
+    Node32(Node32),
+    Node64(Node64),
+    Node96(Node96),
+    Node136(Node136),
+    Node224(Node224),
+}
+
+impl From<Node32> for SizeNode {
+    fn from(value: Node32) -> Self {
+        Self::Node32(value)
+    }
+}
+
+impl From<Node64> for SizeNode {
+    fn from(value: Node64) -> Self {
+        Self::Node64(value)
+    }
+}
+
+impl From<Node96> for SizeNode {
+    fn from(value: Node96) -> Self {
+        Self::Node96(value)
+    }
+}
+
+impl From<Node136> for SizeNode {
+    fn from(value: Node136) -> Self {
+        Self::Node136(value)
+    }
+}
+
+impl From<Node224> for SizeNode {
+    fn from(value: Node224) -> Self {
+        Self::Node224(value)
+    }
+}
+// parameter to avoid multiple enums
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub enum Node<'nodes> {
+    Expression(Expression<'nodes>),
+    Relational(Relational<'nodes>),
+    Acl(Acl<'nodes>),
+    Ddl(Ddl<'nodes>),
+    Block(Block<'nodes>),
+    Parameter(&'nodes Parameter),
+    Invalid(&'nodes Invalid),
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum MutNode<'nodes> {
+    Expression(MutExpression<'nodes>),
+    Relational(MutRelational<'nodes>),
+    Acl(MutAcl<'nodes>),
+    Ddl(MutDdl<'nodes>),
+    Block(MutBlock<'nodes>),
+    Parameter(&'nodes mut Parameter),
+    Invalid(&'nodes mut Invalid),
+}
+
+impl MutNode<'_> {
+    #[must_use]
+    pub fn get_common_node(self) -> NodeOwned {
+        match self {
+            MutNode::Expression(expr) => NodeOwned::Expression(expr.get_expr_owned()),
+            MutNode::Relational(rel) => NodeOwned::Relational(rel.get_rel_owned()),
+            MutNode::Ddl(ddl) => NodeOwned::Ddl(ddl.get_ddl_owned()),
+            MutNode::Acl(acl) => NodeOwned::Acl(acl.get_acl_owned()),
+            MutNode::Block(block) => NodeOwned::Block(block.get_block_owned()),
+            MutNode::Parameter(param) => NodeOwned::Parameter((*param).clone()),
+            MutNode::Invalid(inv) => NodeOwned::Invalid((*inv).clone()),
+        }
+    }
+}
+
+impl Node<'_> {
+    #[must_use]
+    pub fn get_common_node(self) -> NodeOwned {
+        match self {
+            Node::Expression(expr) => NodeOwned::Expression(expr.get_expr_owned()),
+            Node::Relational(rel) => NodeOwned::Relational(rel.get_rel_owned()),
+            Node::Ddl(ddl) => NodeOwned::Ddl(ddl.get_ddl_owned()),
+            Node::Acl(acl) => NodeOwned::Acl(acl.get_acl_owned()),
+            Node::Block(block) => NodeOwned::Block(block.get_block_owned()),
+            Node::Parameter(param) => NodeOwned::Parameter((*param).clone()),
+            Node::Invalid(inv) => NodeOwned::Invalid((*inv).clone()),
+        }
+    }
+}
+
+// rename to NodeOwned
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum NodeOwned {
+    Expression(ExprOwned),
+    Relational(RelOwned),
+    Ddl(DdlOwned),
+    Acl(AclOwned),
+    Block(BlockOwned),
+    Parameter(Parameter),
+    Invalid(Invalid),
+}
+
+impl From<NodeOwned> for SizeNode {
+    fn from(value: NodeOwned) -> Self {
+        match value {
+            NodeOwned::Acl(acl) => acl.into(),
+            NodeOwned::Block(block) => block.into(),
+            NodeOwned::Ddl(ddl) => ddl.into(),
+            NodeOwned::Expression(expr) => expr.into(),
+            NodeOwned::Invalid(inv) => inv.into(),
+            NodeOwned::Parameter(param) => param.into(),
+            NodeOwned::Relational(rel) => rel.into(),
+        }
+    }
+}
diff --git a/sbroad-core/src/ir/node/acl.rs b/sbroad-core/src/ir/node/acl.rs
new file mode 100644
index 000000000..b75a9bc81
--- /dev/null
+++ b/sbroad-core/src/ir/node/acl.rs
@@ -0,0 +1,121 @@
+use serde::Serialize;
+use smol_str::{format_smolstr, ToSmolStr};
+
+use crate::errors::{Entity, SbroadError};
+
+use super::{
+    AlterUser, CreateRole, CreateUser, DropRole, DropUser, GrantPrivilege, RevokePrivilege,
+    SizeNode,
+};
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum AclOwned {
+    DropRole(DropRole),
+    DropUser(DropUser),
+    CreateRole(CreateRole),
+    CreateUser(CreateUser),
+    AlterUser(AlterUser),
+    GrantPrivilege(GrantPrivilege),
+    RevokePrivilege(RevokePrivilege),
+}
+
+impl From<AclOwned> for SizeNode {
+    fn from(value: AclOwned) -> Self {
+        match value {
+            AclOwned::AlterUser(alter_user) => alter_user.into(),
+            AclOwned::CreateRole(create_role) => create_role.into(),
+            AclOwned::CreateUser(create_user) => create_user.into(),
+            AclOwned::DropRole(drop_role) => drop_role.into(),
+            AclOwned::DropUser(drop_user) => drop_user.into(),
+            AclOwned::GrantPrivilege(grant_privilege) => grant_privilege.into(),
+            AclOwned::RevokePrivilege(revoke_privilege) => revoke_privilege.into(),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, PartialEq, Eq, Serialize)]
+pub enum MutAcl<'a> {
+    DropRole(&'a mut DropRole),
+    DropUser(&'a mut DropUser),
+    CreateRole(&'a mut CreateRole),
+    CreateUser(&'a mut CreateUser),
+    AlterUser(&'a mut AlterUser),
+    GrantPrivilege(&'a mut GrantPrivilege),
+    RevokePrivilege(&'a mut RevokePrivilege),
+}
+
+impl MutAcl<'_> {
+    #[must_use]
+    pub fn get_acl_owned(&self) -> AclOwned {
+        match self {
+            MutAcl::AlterUser(alter_user) => AclOwned::AlterUser((*alter_user).clone()),
+            MutAcl::CreateRole(create_role) => AclOwned::CreateRole((*create_role).clone()),
+            MutAcl::CreateUser(create_user) => AclOwned::CreateUser((*create_user).clone()),
+            MutAcl::DropRole(drop_role) => AclOwned::DropRole((*drop_role).clone()),
+            MutAcl::DropUser(drop_user) => AclOwned::DropUser((*drop_user).clone()),
+            MutAcl::GrantPrivilege(grant_privelege) => {
+                AclOwned::GrantPrivilege((*grant_privelege).clone())
+            }
+            MutAcl::RevokePrivilege(revoke_privelege) => {
+                AclOwned::RevokePrivilege((*revoke_privelege).clone())
+            }
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum Acl<'a> {
+    DropRole(&'a DropRole),
+    DropUser(&'a DropUser),
+    CreateRole(&'a CreateRole),
+    CreateUser(&'a CreateUser),
+    AlterUser(&'a AlterUser),
+    GrantPrivilege(&'a GrantPrivilege),
+    RevokePrivilege(&'a RevokePrivilege),
+}
+
+impl Acl<'_> {
+    /// Return ACL node timeout.
+    ///
+    /// # Errors
+    /// - timeout parsing error
+    pub fn timeout(&self) -> Result<f64, SbroadError> {
+        match self {
+            Acl::DropRole(DropRole { ref timeout, .. })
+            | Acl::DropUser(DropUser { ref timeout, .. })
+            | Acl::CreateRole(CreateRole { ref timeout, .. })
+            | Acl::AlterUser(AlterUser { ref timeout, .. })
+            | Acl::CreateUser(CreateUser { ref timeout, .. })
+            | Acl::RevokePrivilege(RevokePrivilege { ref timeout, .. })
+            | Acl::GrantPrivilege(GrantPrivilege { ref timeout, .. }) => timeout,
+        }
+        .to_smolstr()
+        .parse()
+        .map_err(|e| {
+            SbroadError::Invalid(
+                Entity::SpaceMetadata,
+                Some(format_smolstr!("timeout parsing error {e:?}")),
+            )
+        })
+    }
+
+    #[must_use]
+    pub fn get_acl_owned(&self) -> AclOwned {
+        match self {
+            Acl::AlterUser(alter_user) => AclOwned::AlterUser((*alter_user).clone()),
+            Acl::CreateRole(create_role) => AclOwned::CreateRole((*create_role).clone()),
+            Acl::CreateUser(create_user) => AclOwned::CreateUser((*create_user).clone()),
+            Acl::DropRole(drop_role) => AclOwned::DropRole((*drop_role).clone()),
+            Acl::DropUser(drop_user) => AclOwned::DropUser((*drop_user).clone()),
+            Acl::GrantPrivilege(grant_privelege) => {
+                AclOwned::GrantPrivilege((*grant_privelege).clone())
+            }
+            Acl::RevokePrivilege(revoke_privelege) => {
+                AclOwned::RevokePrivilege((*revoke_privelege).clone())
+            }
+        }
+    }
+}
diff --git a/sbroad-core/src/ir/node/block.rs b/sbroad-core/src/ir/node/block.rs
new file mode 100644
index 000000000..e36f164c1
--- /dev/null
+++ b/sbroad-core/src/ir/node/block.rs
@@ -0,0 +1,50 @@
+use serde::Serialize;
+
+use super::{Procedure, SizeNode};
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
+pub enum BlockOwned {
+    /// Procedure body.
+    Procedure(Procedure),
+}
+
+impl From<BlockOwned> for SizeNode {
+    fn from(value: BlockOwned) -> Self {
+        match value {
+            BlockOwned::Procedure(proc) => proc.into(),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Eq, PartialEq, Serialize)]
+pub enum MutBlock<'a> {
+    /// Procedure body.
+    Procedure(&'a mut Procedure),
+}
+
+impl MutBlock<'_> {
+    #[must_use]
+    pub fn get_block_owned(&self) -> BlockOwned {
+        match self {
+            MutBlock::Procedure(proc) => BlockOwned::Procedure((*proc).clone()),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
+pub enum Block<'a> {
+    /// Procedure body.
+    Procedure(&'a Procedure),
+}
+
+impl Block<'_> {
+    #[must_use]
+    pub fn get_block_owned(&self) -> BlockOwned {
+        match self {
+            Block::Procedure(proc) => BlockOwned::Procedure((*proc).clone()),
+        }
+    }
+}
diff --git a/sbroad-core/src/ir/node/ddl.rs b/sbroad-core/src/ir/node/ddl.rs
new file mode 100644
index 000000000..53f2b058b
--- /dev/null
+++ b/sbroad-core/src/ir/node/ddl.rs
@@ -0,0 +1,134 @@
+use serde::Serialize;
+use smol_str::{format_smolstr, ToSmolStr};
+
+use crate::errors::{Entity, SbroadError};
+
+use super::{
+    AlterSystem, CreateIndex, CreateProc, CreateTable, DropIndex, DropProc, DropTable,
+    RenameRoutine, SetParam, SetTransaction, SizeNode,
+};
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum DdlOwned {
+    CreateTable(CreateTable),
+    DropTable(DropTable),
+    CreateProc(CreateProc),
+    DropProc(DropProc),
+    RenameRoutine(RenameRoutine),
+    AlterSystem(AlterSystem),
+    CreateIndex(CreateIndex),
+    DropIndex(DropIndex),
+    SetParam(SetParam),
+    SetTransaction(SetTransaction),
+}
+
+impl From<DdlOwned> for SizeNode {
+    fn from(value: DdlOwned) -> Self {
+        match value {
+            DdlOwned::CreateIndex(create_index) => create_index.into(),
+            DdlOwned::CreateProc(create_proc) => create_proc.into(),
+            DdlOwned::CreateTable(create_table) => create_table.into(),
+            DdlOwned::DropIndex(drop_index) => drop_index.into(),
+            DdlOwned::DropProc(drop_proc) => drop_proc.into(),
+            DdlOwned::DropTable(drop_table) => drop_table.into(),
+            DdlOwned::AlterSystem(alter_system) => alter_system.into(),
+            DdlOwned::RenameRoutine(rename) => rename.into(),
+            DdlOwned::SetParam(set_param) => set_param.into(),
+            DdlOwned::SetTransaction(set_trans) => set_trans.into(),
+        }
+    }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, PartialEq, Eq, Serialize)]
+pub enum MutDdl<'a> {
+    CreateTable(&'a mut CreateTable),
+    DropTable(&'a mut DropTable),
+    CreateProc(&'a mut CreateProc),
+    DropProc(&'a mut DropProc),
+    RenameRoutine(&'a mut RenameRoutine),
+    AlterSystem(&'a mut AlterSystem),
+    CreateIndex(&'a mut CreateIndex),
+    DropIndex(&'a mut DropIndex),
+    SetParam(&'a mut SetParam),
+    SetTransaction(&'a mut SetTransaction),
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum Ddl<'a> {
+    CreateTable(&'a CreateTable),
+    DropTable(&'a DropTable),
+    CreateProc(&'a CreateProc),
+    DropProc(&'a DropProc),
+    RenameRoutine(&'a RenameRoutine),
+    AlterSystem(&'a AlterSystem),
+    CreateIndex(&'a CreateIndex),
+    DropIndex(&'a DropIndex),
+    SetParam(&'a SetParam),
+    SetTransaction(&'a SetTransaction),
+}
+
+impl MutDdl<'_> {
+    #[must_use]
+    pub fn get_ddl_owned(&self) -> DdlOwned {
+        match self {
+            MutDdl::CreateIndex(create_index) => DdlOwned::CreateIndex((*create_index).clone()),
+            MutDdl::CreateProc(create_proc) => DdlOwned::CreateProc((*create_proc).clone()),
+            MutDdl::CreateTable(create_table) => DdlOwned::CreateTable((*create_table).clone()),
+            MutDdl::DropIndex(drop_index) => DdlOwned::DropIndex((*drop_index).clone()),
+            MutDdl::DropProc(drop_proc) => DdlOwned::DropProc((*drop_proc).clone()),
+            MutDdl::DropTable(drop_table) => DdlOwned::DropTable((*drop_table).clone()),
+            MutDdl::RenameRoutine(rename) => DdlOwned::RenameRoutine((*rename).clone()),
+            MutDdl::AlterSystem(alter_system) => DdlOwned::AlterSystem((*alter_system).clone()),
+            MutDdl::SetParam(set_param) => DdlOwned::SetParam((*set_param).clone()),
+            MutDdl::SetTransaction(set_trans) => DdlOwned::SetTransaction((*set_trans).clone()),
+        }
+    }
+}
+
+impl Ddl<'_> {
+    /// Return DDL node timeout.
+    ///
+    /// # Errors
+    /// - timeout parsing error
+    pub fn timeout(&self) -> Result<f64, SbroadError> {
+        match self {
+            Ddl::CreateTable(CreateTable { ref timeout, .. })
+            | Ddl::DropTable(DropTable { ref timeout, .. })
+            | Ddl::CreateIndex(CreateIndex { ref timeout, .. })
+            | Ddl::DropIndex(DropIndex { ref timeout, .. })
+            | Ddl::SetParam(SetParam { ref timeout, .. })
+            | Ddl::SetTransaction(SetTransaction { ref timeout, .. })
+            | Ddl::AlterSystem(AlterSystem { ref timeout, .. })
+            | Ddl::CreateProc(CreateProc { ref timeout, .. })
+            | Ddl::DropProc(DropProc { ref timeout, .. })
+            | Ddl::RenameRoutine(RenameRoutine { ref timeout, .. }) => timeout,
+        }
+        .to_smolstr()
+        .parse()
+        .map_err(|e| {
+            SbroadError::Invalid(
+                Entity::SpaceMetadata,
+                Some(format_smolstr!("timeout parsing error {e:?}")),
+            )
+        })
+    }
+
+    #[must_use]
+    pub fn get_ddl_owned(&self) -> DdlOwned {
+        match self {
+            Ddl::CreateIndex(create_index) => DdlOwned::CreateIndex((*create_index).clone()),
+            Ddl::CreateProc(create_proc) => DdlOwned::CreateProc((*create_proc).clone()),
+            Ddl::CreateTable(create_table) => DdlOwned::CreateTable((*create_table).clone()),
+            Ddl::DropIndex(drop_index) => DdlOwned::DropIndex((*drop_index).clone()),
+            Ddl::DropProc(drop_proc) => DdlOwned::DropProc((*drop_proc).clone()),
+            Ddl::DropTable(drop_table) => DdlOwned::DropTable((*drop_table).clone()),
+            Ddl::AlterSystem(alter_system) => DdlOwned::AlterSystem((*alter_system).clone()),
+            Ddl::RenameRoutine(rename) => DdlOwned::RenameRoutine((*rename).clone()),
+            Ddl::SetParam(set_param) => DdlOwned::SetParam((*set_param).clone()),
+            Ddl::SetTransaction(set_trans) => DdlOwned::SetTransaction((*set_trans).clone()),
+        }
+    }
+}
diff --git a/sbroad-core/src/ir/node/expression.rs b/sbroad-core/src/ir/node/expression.rs
new file mode 100644
index 000000000..e748a01bc
--- /dev/null
+++ b/sbroad-core/src/ir/node/expression.rs
@@ -0,0 +1,263 @@
+use serde::Serialize;
+
+use crate::{
+    errors::{Entity, SbroadError},
+    ir::{aggregates::AggregateKind, distribution::Distribution},
+};
+
+use super::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Constant, CountAsterisk,
+    ExprInParentheses, NodeId, Reference, Row, SizeNode, StableFunction, Trim, UnaryExpr,
+};
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum ExprOwned {
+    Alias(Alias),
+    Bool(BoolExpr),
+    Arithmetic(ArithmeticExpr),
+    Cast(Cast),
+    Concat(Concat),
+    Constant(Constant),
+    Reference(Reference),
+    Row(Row),
+    StableFunction(StableFunction),
+    Trim(Trim),
+    Unary(UnaryExpr),
+    CountAsterisk(CountAsterisk),
+    Case(Case),
+    ExprInParentheses(ExprInParentheses),
+}
+
+impl From<ExprOwned> for SizeNode {
+    fn from(value: ExprOwned) -> Self {
+        match value {
+            ExprOwned::Alias(alias) => alias.into(),
+            ExprOwned::Arithmetic(arithm) => arithm.into(),
+            ExprOwned::Bool(bool) => bool.into(),
+            ExprOwned::Case(case) => case.into(),
+            ExprOwned::Cast(cast) => cast.into(),
+            ExprOwned::Concat(concat) => concat.into(),
+            ExprOwned::Constant(constant) => constant.into(),
+            ExprOwned::CountAsterisk(count) => count.into(),
+            ExprOwned::ExprInParentheses(expr) => expr.into(),
+            ExprOwned::Reference(reference) => reference.into(),
+            ExprOwned::Row(row) => row.into(),
+            ExprOwned::StableFunction(stable_func) => stable_func.into(),
+            ExprOwned::Trim(trim) => trim.into(),
+            ExprOwned::Unary(unary) => unary.into(),
+        }
+    }
+}
+
+/// Tuple tree build blocks.
+///
+/// A tuple describes a single portion of data moved among cluster nodes.
+/// It consists of the ordered, strictly typed expressions with names
+/// (columns) and additional information about data distribution policy.
+///
+/// Tuple is a tree with a `Row` top (level 0) and a list of the named
+/// `Alias` columns (level 1). This convention is used across the code
+/// and should not be changed. It ensures that we always know the
+/// name of any column in the tuple and therefore simplifies AST
+/// deserialization.
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum Expression<'a> {
+    Alias(&'a Alias),
+    Bool(&'a BoolExpr),
+    Arithmetic(&'a ArithmeticExpr),
+    Cast(&'a Cast),
+    Concat(&'a Concat),
+    Constant(&'a Constant),
+    Reference(&'a Reference),
+    Row(&'a Row),
+    StableFunction(&'a StableFunction),
+    Trim(&'a Trim),
+    Unary(&'a UnaryExpr),
+    CountAsterisk(&'a CountAsterisk),
+    Case(&'a Case),
+    ExprInParentheses(&'a ExprInParentheses),
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum MutExpression<'a> {
+    Alias(&'a mut Alias),
+    Bool(&'a mut BoolExpr),
+    Arithmetic(&'a mut ArithmeticExpr),
+    Cast(&'a mut Cast),
+    Concat(&'a mut Concat),
+    Constant(&'a mut Constant),
+    Reference(&'a mut Reference),
+    Row(&'a mut Row),
+    StableFunction(&'a mut StableFunction),
+    Trim(&'a mut Trim),
+    Unary(&'a mut UnaryExpr),
+    CountAsterisk(&'a mut CountAsterisk),
+    Case(&'a mut Case),
+    ExprInParentheses(&'a mut ExprInParentheses),
+}
+
+#[allow(dead_code)]
+impl Expression<'_> {
+    /// Gets current row distribution.
+    ///
+    /// # Errors
+    /// Returns `SbroadError` when the function is called on expression
+    /// other than `Row` or a node doesn't know its distribution yet.
+    pub fn distribution(&self) -> Result<&Distribution, SbroadError> {
+        if let Expression::Row(Row { distribution, .. }) = self {
+            let Some(dist) = distribution else {
+                return Err(SbroadError::Invalid(
+                    Entity::Distribution,
+                    Some("distribution is uninitialized".into()),
+                ));
+            };
+            return Ok(dist);
+        }
+        Err(SbroadError::Invalid(Entity::Expression, None))
+    }
+
+    /// Clone the row children list.
+    ///
+    /// # Errors
+    /// - node isn't `Row`
+    pub fn clone_row_list(&self) -> Result<Vec<NodeId>, SbroadError> {
+        match self {
+            Expression::Row(Row { list, .. }) => Ok(list.clone()),
+            _ => Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("node isn't Row type".into()),
+            )),
+        }
+    }
+
+    #[must_use]
+    pub fn is_aggregate_name(name: &str) -> bool {
+        // currently we support only simple aggregates
+        AggregateKind::new(name).is_some()
+    }
+
+    #[must_use]
+    pub fn is_aggregate_fun(&self) -> bool {
+        match self {
+            Expression::StableFunction(StableFunction { name, .. }) => {
+                Expression::is_aggregate_name(name)
+            }
+            _ => false,
+        }
+    }
+
+    /// Checks for distribution determination
+    ///
+    /// # Errors
+    /// - distribution isn't set
+    pub fn has_unknown_distribution(&self) -> Result<bool, SbroadError> {
+        let d = self.distribution()?;
+        Ok(d.is_unknown())
+    }
+
+    /// Gets relational node id containing the reference.
+    ///
+    /// # Errors
+    /// - node isn't reference type
+    /// - reference doesn't have a parent
+    pub fn get_parent(&self) -> Result<NodeId, SbroadError> {
+        if let Expression::Reference(Reference { parent, .. }) = self {
+            return parent.ok_or_else(|| {
+                SbroadError::Invalid(Entity::Expression, Some("Reference has no parent".into()))
+            });
+        }
+        Err(SbroadError::Invalid(
+            Entity::Expression,
+            Some("node is not Reference type".into()),
+        ))
+    }
+
+    /// The node is a row expression.
+    #[must_use]
+    pub fn is_row(&self) -> bool {
+        matches!(self, Expression::Row(_))
+    }
+    #[must_use]
+    pub fn is_arithmetic(&self) -> bool {
+        matches!(self, Expression::Arithmetic(_))
+    }
+
+    #[must_use]
+    pub fn get_expr_owned(&self) -> ExprOwned {
+        match self {
+            Expression::Alias(alias) => ExprOwned::Alias((*alias).clone()),
+            Expression::Arithmetic(arithm) => ExprOwned::Arithmetic((*arithm).clone()),
+            Expression::Bool(bool) => ExprOwned::Bool((*bool).clone()),
+            Expression::Case(case) => ExprOwned::Case((*case).clone()),
+            Expression::Cast(cast) => ExprOwned::Cast((*cast).clone()),
+            Expression::Concat(con) => ExprOwned::Concat((*con).clone()),
+            Expression::Constant(constant) => ExprOwned::Constant((*constant).clone()),
+            Expression::CountAsterisk(count) => ExprOwned::CountAsterisk((*count).clone()),
+            Expression::ExprInParentheses(expr_par) => {
+                ExprOwned::ExprInParentheses((*expr_par).clone())
+            }
+            Expression::Reference(reference) => ExprOwned::Reference((*reference).clone()),
+            Expression::Row(row) => ExprOwned::Row((*row).clone()),
+            Expression::StableFunction(sfunc) => ExprOwned::StableFunction((*sfunc).clone()),
+            Expression::Trim(trim) => ExprOwned::Trim((*trim).clone()),
+            Expression::Unary(unary) => ExprOwned::Unary((*unary).clone()),
+        }
+    }
+}
+
+impl MutExpression<'_> {
+    /// Get a mutable reference to the row children list.
+    ///
+    /// # Errors
+    /// - node isn't `Row`
+    pub fn get_row_list_mut(&mut self) -> Result<&mut Vec<NodeId>, SbroadError> {
+        match self {
+            MutExpression::Row(Row { ref mut list, .. }) => Ok(list),
+            _ => Err(SbroadError::Invalid(
+                Entity::Expression,
+                Some("node isn't Row type".into()),
+            )),
+        }
+    }
+
+    /// Replaces parent in the reference node with the new one.
+    pub fn replace_parent_in_reference(&mut self, from_id: Option<NodeId>, to_id: Option<NodeId>) {
+        if let MutExpression::Reference(Reference { parent, .. }) = self {
+            if *parent == from_id {
+                *parent = to_id;
+            }
+        }
+    }
+
+    /// Flushes parent in the reference node.
+    pub fn flush_parent_in_reference(&mut self) {
+        if let MutExpression::Reference(Reference { parent, .. }) = self {
+            *parent = None;
+        }
+    }
+
+    #[must_use]
+    pub fn get_expr_owned(&self) -> ExprOwned {
+        match self {
+            MutExpression::Alias(alias) => ExprOwned::Alias((*alias).clone()),
+            MutExpression::Arithmetic(arithm) => ExprOwned::Arithmetic((*arithm).clone()),
+            MutExpression::Bool(bool) => ExprOwned::Bool((*bool).clone()),
+            MutExpression::Case(case) => ExprOwned::Case((*case).clone()),
+            MutExpression::Cast(cast) => ExprOwned::Cast((*cast).clone()),
+            MutExpression::Concat(con) => ExprOwned::Concat((*con).clone()),
+            MutExpression::Constant(constant) => ExprOwned::Constant((*constant).clone()),
+            MutExpression::CountAsterisk(count) => ExprOwned::CountAsterisk((*count).clone()),
+            MutExpression::ExprInParentheses(expr_par) => {
+                ExprOwned::ExprInParentheses((*expr_par).clone())
+            }
+            MutExpression::Reference(reference) => ExprOwned::Reference((*reference).clone()),
+            MutExpression::Row(row) => ExprOwned::Row((*row).clone()),
+            MutExpression::StableFunction(sfunc) => ExprOwned::StableFunction((*sfunc).clone()),
+            MutExpression::Trim(trim) => ExprOwned::Trim((*trim).clone()),
+            MutExpression::Unary(unary) => ExprOwned::Unary((*unary).clone()),
+        }
+    }
+}
diff --git a/sbroad-core/src/ir/node/relational.rs b/sbroad-core/src/ir/node/relational.rs
new file mode 100644
index 000000000..c580db51f
--- /dev/null
+++ b/sbroad-core/src/ir/node/relational.rs
@@ -0,0 +1,704 @@
+use serde::Serialize;
+use smol_str::SmolStr;
+
+use crate::{
+    errors::{Entity, SbroadError},
+    ir::api::children::{Children, MutChildren},
+};
+
+use super::{
+    Delete, Except, GroupBy, Having, Insert, Intersect, Join, Limit, Motion, NodeId, OrderBy,
+    Projection, ScanCte, ScanRelation, ScanSubQuery, Selection, SizeNode, Union, UnionAll, Update,
+    Values, ValuesRow,
+};
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum RelOwned {
+    ScanCte(ScanCte),
+    Except(Except),
+    Delete(Delete),
+    Insert(Insert),
+    Intersect(Intersect),
+    Update(Update),
+    Join(Join),
+    Limit(Limit),
+    Motion(Motion),
+    Projection(Projection),
+    ScanRelation(ScanRelation),
+    ScanSubQuery(ScanSubQuery),
+    Selection(Selection),
+    GroupBy(GroupBy),
+    Having(Having),
+    OrderBy(OrderBy),
+    UnionAll(UnionAll),
+    Union(Union),
+    Values(Values),
+    ValuesRow(ValuesRow),
+}
+
+impl From<RelOwned> for SizeNode {
+    fn from(value: RelOwned) -> Self {
+        match value {
+            RelOwned::ScanCte(scan_cte) => scan_cte.into(),
+            RelOwned::Delete(delete) => delete.into(),
+            RelOwned::Except(except) => except.into(),
+            RelOwned::GroupBy(group_by) => group_by.into(),
+            RelOwned::Having(having) => having.into(),
+            RelOwned::Insert(insert) => insert.into(),
+            RelOwned::Intersect(intersect) => intersect.into(),
+            RelOwned::Join(join) => join.into(),
+            RelOwned::Limit(limit) => limit.into(),
+            RelOwned::Motion(motion) => motion.into(),
+            RelOwned::OrderBy(order_by) => order_by.into(),
+            RelOwned::Projection(proj) => proj.into(),
+            RelOwned::ScanRelation(scan_rel) => scan_rel.into(),
+            RelOwned::ScanSubQuery(scan_squery) => scan_squery.into(),
+            RelOwned::Selection(selection) => selection.into(),
+            RelOwned::Union(un) => un.into(),
+            RelOwned::UnionAll(union_all) => union_all.into(),
+            RelOwned::Update(update) => update.into(),
+            RelOwned::Values(values) => values.into(),
+            RelOwned::ValuesRow(values_row) => values_row.into(),
+        }
+    }
+}
+
+impl RelOwned {
+    /// Sets new children to relational node.
+    ///
+    /// # Panics
+    /// - wrong number of children for the given node
+    pub fn set_children(&mut self, children: Vec<NodeId>) {
+        match self {
+            RelOwned::Join(Join {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Delete(Delete {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Update(Update {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Insert(Insert {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Motion(Motion {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Projection(Projection {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::ScanSubQuery(ScanSubQuery {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Selection(Selection {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Values(Values {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::GroupBy(GroupBy {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::Having(Having {
+                children: ref mut old,
+                ..
+            })
+            | RelOwned::ValuesRow(ValuesRow {
+                children: ref mut old,
+                ..
+            }) => {
+                *old = children;
+            }
+            RelOwned::Except(Except { left, right, .. })
+            | RelOwned::UnionAll(UnionAll { left, right, .. })
+            | RelOwned::Intersect(Intersect { left, right, .. })
+            | RelOwned::Union(Union { left, right, .. }) => {
+                if children.len() != 2 {
+                    unreachable!("Node has only two children!");
+                }
+                *left = children[0];
+                *right = children[1];
+            }
+            RelOwned::OrderBy(OrderBy { ref mut child, .. }) => {
+                if children.len() != 1 {
+                    unreachable!("ORDER BY may have only a single relational child");
+                }
+                // It is safe to unwrap here, because the length is already checked above.
+                *child = children[0];
+            }
+            RelOwned::ScanCte(ScanCte { ref mut child, .. }) => {
+                if children.len() != 1 {
+                    unreachable!("CTE may have only a single relational child");
+                }
+                // It is safe to unwrap here, because the length is already checked above.
+                *child = children[0];
+            }
+            RelOwned::Limit(Limit { ref mut child, .. }) => {
+                if children.len() != 1 {
+                    unreachable!("LIMIT may have only a single relational child");
+                }
+                // It is safe to unwrap here, because the length is already checked above.
+                *child = children[0];
+            }
+            RelOwned::ScanRelation(ScanRelation { .. }) => {
+                assert!(children.is_empty(), "scan must have no children!");
+            }
+        }
+    }
+
+    // Gets an immutable reference to the children nodes.
+    #[must_use]
+    pub fn children(&self) -> Children<'_> {
+        match self {
+            RelOwned::Limit(Limit { child, .. })
+            | RelOwned::OrderBy(OrderBy { child, .. })
+            | RelOwned::ScanCte(ScanCte { child, .. }) => Children::Single(child),
+            RelOwned::Except(Except { left, right, .. })
+            | RelOwned::Intersect(Intersect { left, right, .. })
+            | RelOwned::UnionAll(UnionAll { left, right, .. })
+            | RelOwned::Union(Union { left, right, .. }) => Children::Couple(left, right),
+            RelOwned::GroupBy(GroupBy { children, .. })
+            | RelOwned::Update(Update { children, .. })
+            | RelOwned::Join(Join { children, .. })
+            | RelOwned::Having(Having { children, .. })
+            | RelOwned::Delete(Delete { children, .. })
+            | RelOwned::Insert(Insert { children, .. })
+            | RelOwned::Motion(Motion { children, .. })
+            | RelOwned::Projection(Projection { children, .. })
+            | RelOwned::ScanSubQuery(ScanSubQuery { children, .. })
+            | RelOwned::Selection(Selection { children, .. })
+            | RelOwned::ValuesRow(ValuesRow { children, .. })
+            | RelOwned::Values(Values { children, .. }) => Children::Many(children),
+            RelOwned::ScanRelation(_) => Children::None,
+        }
+    }
+
+    #[must_use]
+    pub fn mut_children(&mut self) -> MutChildren<'_> {
+        match self {
+            RelOwned::Limit(Limit { ref mut child, .. })
+            | RelOwned::OrderBy(OrderBy { ref mut child, .. })
+            | RelOwned::ScanCte(ScanCte { ref mut child, .. }) => MutChildren::Single(child),
+            RelOwned::Except(Except {
+                ref mut left,
+                ref mut right,
+                ..
+            })
+            | RelOwned::Intersect(Intersect {
+                ref mut left,
+                ref mut right,
+                ..
+            })
+            | RelOwned::UnionAll(UnionAll {
+                ref mut left,
+                ref mut right,
+                ..
+            })
+            | RelOwned::Union(Union {
+                ref mut left,
+                ref mut right,
+                ..
+            }) => MutChildren::Couple(left, right),
+            RelOwned::GroupBy(GroupBy {
+                ref mut children, ..
+            })
+            | RelOwned::Update(Update {
+                ref mut children, ..
+            })
+            | RelOwned::Join(Join {
+                ref mut children, ..
+            })
+            | RelOwned::Having(Having {
+                ref mut children, ..
+            })
+            | RelOwned::Delete(Delete {
+                ref mut children, ..
+            })
+            | RelOwned::Insert(Insert {
+                ref mut children, ..
+            })
+            | RelOwned::Motion(Motion {
+                ref mut children, ..
+            })
+            | RelOwned::Projection(Projection {
+                ref mut children, ..
+            })
+            | RelOwned::ScanSubQuery(ScanSubQuery {
+                ref mut children, ..
+            })
+            | RelOwned::Selection(Selection {
+                ref mut children, ..
+            })
+            | RelOwned::ValuesRow(ValuesRow {
+                ref mut children, ..
+            })
+            | RelOwned::Values(Values {
+                ref mut children, ..
+            }) => MutChildren::Many(children),
+            RelOwned::ScanRelation(_) => MutChildren::None,
+        }
+    }
+
+    /// Gets an mutable reference to the output tuple node id.
+    #[must_use]
+    pub fn mut_output(&mut self) -> &mut NodeId {
+        match self {
+            RelOwned::ScanCte(ScanCte { output, .. })
+            | RelOwned::Except(Except { output, .. })
+            | RelOwned::GroupBy(GroupBy { output, .. })
+            | RelOwned::OrderBy(OrderBy { output, .. })
+            | RelOwned::Update(Update { output, .. })
+            | RelOwned::Having(Having { output, .. })
+            | RelOwned::Join(Join { output, .. })
+            | RelOwned::Limit(Limit { output, .. })
+            | RelOwned::Delete(Delete { output, .. })
+            | RelOwned::Insert(Insert { output, .. })
+            | RelOwned::Intersect(Intersect { output, .. })
+            | RelOwned::Motion(Motion { output, .. })
+            | RelOwned::Projection(Projection { output, .. })
+            | RelOwned::ScanRelation(ScanRelation { output, .. })
+            | RelOwned::ScanSubQuery(ScanSubQuery { output, .. })
+            | RelOwned::Selection(Selection { output, .. })
+            | RelOwned::Union(Union { output, .. })
+            | RelOwned::UnionAll(UnionAll { output, .. })
+            | RelOwned::Values(Values { output, .. })
+            | RelOwned::ValuesRow(ValuesRow { output, .. }) => output,
+        }
+    }
+}
+
+/// Relational algebra operator returning a new tuple.
+///
+/// Transforms input tuple(s) into the output one using the
+/// relation algebra logic.
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub enum Relational<'a> {
+    ScanCte(&'a ScanCte),
+    Except(&'a Except),
+    Delete(&'a Delete),
+    Insert(&'a Insert),
+    Intersect(&'a Intersect),
+    Update(&'a Update),
+    Join(&'a Join),
+    Limit(&'a Limit),
+    Motion(&'a Motion),
+    Projection(&'a Projection),
+    ScanRelation(&'a ScanRelation),
+    ScanSubQuery(&'a ScanSubQuery),
+    Selection(&'a Selection),
+    GroupBy(&'a GroupBy),
+    Having(&'a Having),
+    OrderBy(&'a OrderBy),
+    UnionAll(&'a UnionAll),
+    Union(&'a Union),
+    Values(&'a Values),
+    ValuesRow(&'a ValuesRow),
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, PartialEq, Eq, Serialize)]
+pub enum MutRelational<'a> {
+    ScanCte(&'a mut ScanCte),
+    Except(&'a mut Except),
+    Delete(&'a mut Delete),
+    Insert(&'a mut Insert),
+    Intersect(&'a mut Intersect),
+    Update(&'a mut Update),
+    Join(&'a mut Join),
+    Limit(&'a mut Limit),
+    Motion(&'a mut Motion),
+    Projection(&'a mut Projection),
+    ScanRelation(&'a mut ScanRelation),
+    ScanSubQuery(&'a mut ScanSubQuery),
+    Selection(&'a mut Selection),
+    GroupBy(&'a mut GroupBy),
+    Having(&'a mut Having),
+    OrderBy(&'a mut OrderBy),
+    UnionAll(&'a mut UnionAll),
+    Union(&'a mut Union),
+    Values(&'a mut Values),
+    ValuesRow(&'a mut ValuesRow),
+}
+
+impl MutRelational<'_> {
+    /// Gets an mutable reference to the output tuple node id.
+    #[must_use]
+    pub fn mut_output(&mut self) -> &mut NodeId {
+        match self {
+            MutRelational::ScanCte(ScanCte { output, .. })
+            | MutRelational::Except(Except { output, .. })
+            | MutRelational::GroupBy(GroupBy { output, .. })
+            | MutRelational::OrderBy(OrderBy { output, .. })
+            | MutRelational::Update(Update { output, .. })
+            | MutRelational::Having(Having { output, .. })
+            | MutRelational::Join(Join { output, .. })
+            | MutRelational::Limit(Limit { output, .. })
+            | MutRelational::Delete(Delete { output, .. })
+            | MutRelational::Insert(Insert { output, .. })
+            | MutRelational::Intersect(Intersect { output, .. })
+            | MutRelational::Motion(Motion { output, .. })
+            | MutRelational::Projection(Projection { output, .. })
+            | MutRelational::ScanRelation(ScanRelation { output, .. })
+            | MutRelational::ScanSubQuery(ScanSubQuery { output, .. })
+            | MutRelational::Selection(Selection { output, .. })
+            | MutRelational::Union(Union { output, .. })
+            | MutRelational::UnionAll(UnionAll { output, .. })
+            | MutRelational::Values(Values { output, .. })
+            | MutRelational::ValuesRow(ValuesRow { output, .. }) => output,
+        }
+    }
+
+    // Gets a mutable reference to the children nodes.
+    #[must_use]
+    pub fn mut_children(&mut self) -> MutChildren<'_> {
+        // return MutChildren { node: self };
+        match self {
+            MutRelational::Limit(Limit { child, .. })
+            | MutRelational::OrderBy(OrderBy { child, .. })
+            | MutRelational::ScanCte(ScanCte { child, .. }) => MutChildren::Single(child),
+            MutRelational::Except(Except { left, right, .. })
+            | MutRelational::Intersect(Intersect { left, right, .. })
+            | MutRelational::UnionAll(UnionAll { left, right, .. })
+            | MutRelational::Union(Union { left, right, .. }) => MutChildren::Couple(left, right),
+            MutRelational::GroupBy(GroupBy {
+                ref mut children, ..
+            })
+            | MutRelational::Update(Update {
+                ref mut children, ..
+            })
+            | MutRelational::Having(Having {
+                ref mut children, ..
+            })
+            | MutRelational::Join(Join {
+                ref mut children, ..
+            })
+            | MutRelational::Delete(Delete {
+                ref mut children, ..
+            })
+            | MutRelational::Insert(Insert {
+                ref mut children, ..
+            })
+            | MutRelational::Motion(Motion {
+                ref mut children, ..
+            })
+            | MutRelational::Projection(Projection {
+                ref mut children, ..
+            })
+            | MutRelational::ScanSubQuery(ScanSubQuery {
+                ref mut children, ..
+            })
+            | MutRelational::Selection(Selection {
+                ref mut children, ..
+            })
+            | MutRelational::ValuesRow(ValuesRow {
+                ref mut children, ..
+            })
+            | MutRelational::Values(Values {
+                ref mut children, ..
+            }) => MutChildren::Many(children),
+            MutRelational::ScanRelation(_) => MutChildren::None,
+        }
+    }
+
+    /// Sets new children to relational node.
+    ///
+    /// # Panics
+    /// - wrong number of children for the given node
+    pub fn set_children(&mut self, children: Vec<NodeId>) {
+        match self {
+            MutRelational::Join(Join {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Delete(Delete {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Update(Update {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Insert(Insert {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Motion(Motion {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Projection(Projection {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::ScanSubQuery(ScanSubQuery {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Selection(Selection {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Values(Values {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::GroupBy(GroupBy {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::Having(Having {
+                children: ref mut old,
+                ..
+            })
+            | MutRelational::ValuesRow(ValuesRow {
+                children: ref mut old,
+                ..
+            }) => {
+                *old = children;
+            }
+            MutRelational::Except(Except { left, right, .. })
+            | MutRelational::UnionAll(UnionAll { left, right, .. })
+            | MutRelational::Intersect(Intersect { left, right, .. })
+            | MutRelational::Union(Union { left, right, .. }) => {
+                if children.len() != 2 {
+                    unreachable!("Node has only two children!");
+                }
+                *left = children[0];
+                *right = children[1];
+            }
+            MutRelational::OrderBy(OrderBy { ref mut child, .. }) => {
+                if children.len() != 1 {
+                    unreachable!("ORDER BY may have only a single relational child");
+                }
+                // It is safe to unwrap here, because the length is already checked above.
+                *child = children[0];
+            }
+            MutRelational::ScanCte(ScanCte { ref mut child, .. }) => {
+                if children.len() != 1 {
+                    unreachable!("CTE may have only a single relational child");
+                }
+                // It is safe to unwrap here, because the length is already checked above.
+                *child = children[0];
+            }
+            MutRelational::Limit(Limit { ref mut child, .. }) => {
+                if children.len() != 1 {
+                    unreachable!("LIMIT may have only a single relational child");
+                }
+                // It is safe to unwrap here, because the length is already checked above.
+                *child = children[0];
+            }
+            MutRelational::ScanRelation(ScanRelation { .. }) => {
+                assert!(children.is_empty(), "scan must have no children!");
+            }
+        }
+    }
+
+    /// Sets new scan name to relational node.
+    ///
+    /// # Errors
+    /// - relational node is not a scan.
+    ///
+    /// # Panics
+    /// - CTE must have a name.
+    pub fn set_scan_name(&mut self, name: Option<SmolStr>) -> Result<(), SbroadError> {
+        match self {
+            MutRelational::ScanRelation(ScanRelation { ref mut alias, .. })
+            | MutRelational::ScanSubQuery(ScanSubQuery { ref mut alias, .. }) => {
+                *alias = name;
+                Ok(())
+            }
+            MutRelational::ScanCte(ScanCte { ref mut alias, .. }) => {
+                let name = name.expect("CTE must have a name");
+                *alias = name;
+                Ok(())
+            }
+            _ => Err(SbroadError::Invalid(
+                Entity::Relational,
+                Some("Relational node is not a Scan.".into()),
+            )),
+        }
+    }
+
+    #[must_use]
+    pub fn get_rel_owned(&self) -> RelOwned {
+        match self {
+            MutRelational::Delete(del) => RelOwned::Delete((*del).clone()),
+            MutRelational::Except(except) => RelOwned::Except((*except).clone()),
+            MutRelational::GroupBy(group_by) => RelOwned::GroupBy((*group_by).clone()),
+            MutRelational::Having(having) => RelOwned::Having((*having).clone()),
+            MutRelational::Insert(insert) => RelOwned::Insert((*insert).clone()),
+            MutRelational::Intersect(intersect) => RelOwned::Intersect((*intersect).clone()),
+            MutRelational::Join(join) => RelOwned::Join((*join).clone()),
+            MutRelational::Limit(limit) => RelOwned::Limit((*limit).clone()),
+            MutRelational::Motion(motion) => RelOwned::Motion((*motion).clone()),
+            MutRelational::OrderBy(order_by) => RelOwned::OrderBy((*order_by).clone()),
+            MutRelational::Projection(proj) => RelOwned::Projection((*proj).clone()),
+            MutRelational::ScanCte(scan_cte) => RelOwned::ScanCte((*scan_cte).clone()),
+            MutRelational::ScanRelation(scan_rel) => RelOwned::ScanRelation((*scan_rel).clone()),
+            MutRelational::ScanSubQuery(ssubquery) => RelOwned::ScanSubQuery((*ssubquery).clone()),
+            MutRelational::Selection(sel) => RelOwned::Selection((*sel).clone()),
+            MutRelational::Union(un) => RelOwned::Union((*un).clone()),
+            MutRelational::UnionAll(union_all) => RelOwned::UnionAll((*union_all).clone()),
+            MutRelational::Update(upd) => RelOwned::Update((*upd).clone()),
+            MutRelational::Values(values) => RelOwned::Values((*values).clone()),
+            MutRelational::ValuesRow(values_row) => RelOwned::ValuesRow((*values_row).clone()),
+        }
+    }
+}
+
+#[allow(dead_code)]
+impl Relational<'_> {
+    /// Gets an immutable id of the output tuple node of the plan's arena.
+    #[must_use]
+    pub fn output(&self) -> NodeId {
+        match self {
+            Relational::ScanCte(ScanCte { output, .. })
+            | Relational::Except(Except { output, .. })
+            | Relational::GroupBy(GroupBy { output, .. })
+            | Relational::OrderBy(OrderBy { output, .. })
+            | Relational::Having(Having { output, .. })
+            | Relational::Update(Update { output, .. })
+            | Relational::Limit(Limit { output, .. })
+            | Relational::Join(Join { output, .. })
+            | Relational::Delete(Delete { output, .. })
+            | Relational::Insert(Insert { output, .. })
+            | Relational::Intersect(Intersect { output, .. })
+            | Relational::Motion(Motion { output, .. })
+            | Relational::Projection(Projection { output, .. })
+            | Relational::ScanRelation(ScanRelation { output, .. })
+            | Relational::ScanSubQuery(ScanSubQuery { output, .. })
+            | Relational::Selection(Selection { output, .. })
+            | Relational::Union(Union { output, .. })
+            | Relational::UnionAll(UnionAll { output, .. })
+            | Relational::Values(Values { output, .. })
+            | Relational::ValuesRow(ValuesRow { output, .. }) => *output,
+        }
+    }
+
+    // Gets an immutable reference to the children nodes.
+    #[must_use]
+    pub fn children(&self) -> Children<'_> {
+        match self {
+            Relational::Limit(Limit { child, .. })
+            | Relational::OrderBy(OrderBy { child, .. })
+            | Relational::ScanCte(ScanCte { child, .. }) => Children::Single(child),
+            Relational::Except(Except { left, right, .. })
+            | Relational::Intersect(Intersect { left, right, .. })
+            | Relational::UnionAll(UnionAll { left, right, .. })
+            | Relational::Union(Union { left, right, .. }) => Children::Couple(left, right),
+            Relational::GroupBy(GroupBy { children, .. })
+            | Relational::Update(Update { children, .. })
+            | Relational::Join(Join { children, .. })
+            | Relational::Having(Having { children, .. })
+            | Relational::Delete(Delete { children, .. })
+            | Relational::Insert(Insert { children, .. })
+            | Relational::Motion(Motion { children, .. })
+            | Relational::Projection(Projection { children, .. })
+            | Relational::ScanSubQuery(ScanSubQuery { children, .. })
+            | Relational::Selection(Selection { children, .. })
+            | Relational::ValuesRow(ValuesRow { children, .. })
+            | Relational::Values(Values { children, .. }) => Children::Many(children),
+            Relational::ScanRelation(_) => Children::None,
+        }
+    }
+
+    /// Checks if the node is deletion.
+    #[must_use]
+    pub fn is_delete(&self) -> bool {
+        matches!(self, Relational::Delete { .. })
+    }
+    /// Checks if the node is an insertion.
+    #[must_use]
+    pub fn is_insert(&self) -> bool {
+        matches!(self, Relational::Insert { .. })
+    }
+
+    /// Checks if the node is dml node
+    #[must_use]
+    pub fn is_dml(&self) -> bool {
+        matches!(
+            self,
+            Relational::Insert { .. } | Relational::Update { .. } | Relational::Delete { .. }
+        )
+    }
+
+    /// Checks that the node is a motion.
+    #[must_use]
+    pub fn is_motion(&self) -> bool {
+        matches!(self, &Relational::Motion { .. })
+    }
+
+    /// Checks that the node is a sub-query or CTE scan.
+    #[must_use]
+    pub fn is_subquery_or_cte(&self) -> bool {
+        matches!(
+            self,
+            &Relational::ScanSubQuery { .. } | &Relational::ScanCte { .. }
+        )
+    }
+
+    #[must_use]
+    pub fn name(&self) -> &str {
+        match self {
+            Relational::Except { .. } => "Except",
+            Relational::Delete { .. } => "Delete",
+            Relational::Insert { .. } => "Insert",
+            Relational::Intersect { .. } => "Intersect",
+            Relational::Update { .. } => "Update",
+            Relational::Join { .. } => "Join",
+            Relational::Limit { .. } => "Limit",
+            Relational::Motion { .. } => "Motion",
+            Relational::Projection { .. } => "Projection",
+            Relational::ScanCte { .. } => "CTE",
+            Relational::ScanRelation { .. } => "Scan",
+            Relational::ScanSubQuery { .. } => "Subquery",
+            Relational::Selection { .. } => "Selection",
+            Relational::GroupBy { .. } => "GroupBy",
+            Relational::OrderBy { .. } => "OrderBy",
+            Relational::Having { .. } => "Having",
+            Relational::Union { .. } => "Union",
+            Relational::UnionAll { .. } => "UnionAll",
+            Relational::Values { .. } => "Values",
+            Relational::ValuesRow { .. } => "ValuesRow",
+        }
+    }
+
+    #[must_use]
+    pub fn get_rel_owned(&self) -> RelOwned {
+        match self {
+            Relational::Delete(del) => RelOwned::Delete((*del).clone()),
+            Relational::Except(except) => RelOwned::Except((*except).clone()),
+            Relational::GroupBy(group_by) => RelOwned::GroupBy((*group_by).clone()),
+            Relational::Having(having) => RelOwned::Having((*having).clone()),
+            Relational::Insert(insert) => RelOwned::Insert((*insert).clone()),
+            Relational::Intersect(intersect) => RelOwned::Intersect((*intersect).clone()),
+            Relational::Join(join) => RelOwned::Join((*join).clone()),
+            Relational::Limit(join) => RelOwned::Limit((*join).clone()),
+            Relational::Motion(motion) => RelOwned::Motion((*motion).clone()),
+            Relational::OrderBy(order_by) => RelOwned::OrderBy((*order_by).clone()),
+            Relational::Projection(proj) => RelOwned::Projection((*proj).clone()),
+            Relational::ScanCte(scan_cte) => RelOwned::ScanCte((*scan_cte).clone()),
+            Relational::ScanRelation(scan_rel) => RelOwned::ScanRelation((*scan_rel).clone()),
+            Relational::ScanSubQuery(ssubquery) => RelOwned::ScanSubQuery((*ssubquery).clone()),
+            Relational::Selection(sel) => RelOwned::Selection((*sel).clone()),
+            Relational::Union(un) => RelOwned::Union((*un).clone()),
+            Relational::UnionAll(union_all) => RelOwned::UnionAll((*union_all).clone()),
+            Relational::Update(upd) => RelOwned::Update((*upd).clone()),
+            Relational::Values(values) => RelOwned::Values((*values).clone()),
+            Relational::ValuesRow(values_row) => RelOwned::ValuesRow((*values_row).clone()),
+        }
+    }
+}
diff --git a/sbroad-core/src/ir/node/util.rs b/sbroad-core/src/ir/node/util.rs
new file mode 100644
index 000000000..a97b82604
--- /dev/null
+++ b/sbroad-core/src/ir/node/util.rs
@@ -0,0 +1,14 @@
+use serde::Serialize;
+use crate::ir::node::{Invalid as Inv, Parameter as Param};
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
+pub enum Invalid<'a> {
+    /// Procedure body.
+    Invalid(&'a Inv),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
+pub enum Parameter<'a> {
+    /// Procedure body.
+    Parameter(&'a Param),
+}
\ No newline at end of file
diff --git a/sbroad-core/src/ir/operator.rs b/sbroad-core/src/ir/operator.rs
index 44728f3ed..ed65415f0 100644
--- a/sbroad-core/src/ir/operator.rs
+++ b/sbroad-core/src/ir/operator.rs
@@ -4,6 +4,14 @@
 
 use crate::executor::engine::helpers::to_user;
 use crate::frontend::sql::get_unnamed_column_alias;
+use crate::ir::api::children::Children;
+use crate::ir::expression::PlanExpr;
+use crate::ir::node::{
+    Alias, Delete, Except, GroupBy, Having, Insert, Intersect, Join, Motion, MutNode, Node64,
+    NodeId, OrderBy, Projection, Reference, Row, ScanCte, ScanRelation, ScanSubQuery, Selection,
+    Union, UnionAll, Update, Values, ValuesRow,
+};
+use crate::ir::Plan;
 use ahash::RandomState;
 
 use crate::collection;
@@ -16,13 +24,13 @@ use std::fmt::{Display, Formatter};
 
 use crate::errors::{Action, Entity, SbroadError};
 
-use super::api::children::{Children, MutChildren};
-use super::expression::{ColumnPositionMap, Expression, NodeId};
+use super::expression::{ColumnPositionMap, ExpressionId};
+use super::node::expression::{Expression, MutExpression};
+use super::node::relational::Relational;
+use super::node::{ArenaType, Limit, Node, SizeNode};
 use super::transformation::redistribution::{MotionPolicy, Program};
 use super::tree::traversal::{LevelNode, PostOrderWithFilter, EXPR_CAPACITY};
-use super::{ArenaType, Node, Plan};
 use crate::ir::distribution::{Distribution, Key, KeySet};
-use crate::ir::expression::{ExpressionId, PlanExpr};
 use crate::ir::helpers::RepeatableState;
 use crate::ir::relation::{Column, ColumnRole};
 use crate::ir::transformation::redistribution::{ColumnPosition, JoinChild};
@@ -312,605 +320,13 @@ pub struct OrderByElement {
     pub order_type: Option<OrderByType>,
 }
 
-/// Relational algebra operator returning a new tuple.
-///
-/// Transforms input tuple(s) into the output one using the
-/// relation algebra logic.
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
-pub enum Relational {
-    ScanCte {
-        /// CTE's name.
-        alias: SmolStr,
-        /// Contains exactly one single element (projection node index).
-        child: NodeId,
-        /// An output tuple with aliases.
-        output: NodeId,
-    },
-    Except {
-        /// Left child id
-        left: NodeId,
-        /// Right child id
-        right: NodeId,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-    },
-    Delete {
-        /// Relation name.
-        relation: SmolStr,
-        /// Contains exactly one single element.
-        children: Vec<NodeId>,
-        /// The output tuple (reserved for `delete returning`).
-        output: NodeId,
-    },
-    Insert {
-        /// Relation name.
-        relation: SmolStr,
-        /// Target column positions for data insertion from
-        /// the child's tuple.
-        columns: Vec<usize>,
-        /// Contains exactly one single element.
-        children: Vec<NodeId>,
-        /// The output tuple (reserved for `insert returning`).
-        output: NodeId,
-        /// What to do in case there is a conflict during insert on storage
-        conflict_strategy: ConflictStrategy,
-    },
-    Intersect {
-        left: NodeId,
-        right: NodeId,
-        // id of the output tuple
-        output: NodeId,
-    },
-    Update {
-        /// Relation name.
-        relation: SmolStr,
-        /// Children ids. Update has exactly one child.
-        children: Vec<NodeId>,
-        /// Maps position of column being updated in table to corresponding position
-        /// in `Projection` below `Update`.
-        ///
-        /// For sharded `Update`, it will contain every table column except `bucket_id`
-        /// column. For local `Update` it will contain only update table columns.
-        update_columns_map: HashMap<ColumnPosition, ColumnPosition, RepeatableState>,
-        /// How this update must be executed.
-        strategy: UpdateStrategy,
-        /// Positions of primary columns in `Projection`
-        /// below `Update`.
-        pk_positions: Vec<ColumnPosition>,
-        /// Output id.
-        output: NodeId,
-    },
-    Join {
-        /// Contains at least two elements: left and right node indexes
-        /// from the plan node arena. Every element other than those
-        /// two should be treated as a `SubQuery` node.
-        children: Vec<NodeId>,
-        /// Left and right tuple comparison condition.
-        /// In fact it is an expression tree top index from the plan node arena.
-        condition: NodeId,
-        /// Outputs tuple node index from the plan node arena.
-        output: NodeId,
-        /// inner or left
-        kind: JoinKind,
-    },
-    Motion {
-        // Scan name.
-        alias: Option<SmolStr>,
-        /// Contains exactly one single element: child node index
-        /// from the plan node arena.
-        children: Vec<NodeId>,
-        /// Motion policy - the amount of data to be moved.
-        policy: MotionPolicy,
-        /// A sequence of opcodes that transform the data.
-        program: Program,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-        /// A helper field indicating whether first element of
-        /// `children` vec is a `Relational::SubQuery`.
-        /// We need it on the stage of translating Plan to SQL, because
-        /// by that moment we've already erased `children` information using
-        /// `unlink_motion_subtree` function.
-        is_child_subquery: bool,
-    },
-    Projection {
-        /// Contains at least one single element: child node index
-        /// from the plan node arena. Every element other than the
-        /// first one should be treated as a `SubQuery` node from
-        /// the output tree.
-        children: Vec<NodeId>,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-        /// Wheter the select was marked with `distinct` keyword
-        is_distinct: bool,
-    },
-    ScanRelation {
-        // Scan name.
-        alias: Option<SmolStr>,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-        /// Relation name.
-        relation: SmolStr,
-    },
-    ScanSubQuery {
-        /// SubQuery name.
-        alias: Option<SmolStr>,
-        /// Contains exactly one single element: child node index
-        /// from the plan node arena.
-        children: Vec<NodeId>,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-    },
-    Selection {
-        /// Contains at least one single element: child node index
-        /// from the plan node arena. Every element other than the
-        /// first one should be treated as a `SubQuery` node from
-        /// the filter tree.
-        children: Vec<NodeId>,
-        /// Filters expression node index in the plan node arena.
-        filter: NodeId,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-    },
-    GroupBy {
-        /// The first child is a relational operator before group by
-        children: Vec<NodeId>,
-        gr_cols: Vec<NodeId>,
-        output: NodeId,
-        is_final: bool,
-    },
-    Having {
-        children: Vec<NodeId>,
-        output: NodeId,
-        filter: NodeId,
-    },
-    OrderBy {
-        child: NodeId,
-        output: NodeId,
-        order_by_elements: Vec<OrderByElement>,
-    },
-    UnionAll {
-        /// Left child id
-        left: NodeId,
-        /// Right child id
-        right: NodeId,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-    },
-    Union {
-        /// Left child id
-        left: NodeId,
-        /// Right child id
-        right: NodeId,
-        /// Outputs tuple node index in the plan node arena.
-        output: NodeId,
-    },
-    Values {
-        /// Output tuple.
-        output: NodeId,
-        /// Non-empty list of value rows.
-        children: Vec<NodeId>,
-    },
-    ValuesRow {
-        /// Output tuple of aliases.
-        output: NodeId,
-        /// The data tuple.
-        data: NodeId,
-        /// A list of children is required for the rows containing
-        /// sub-queries. For example, the row `(1, (select a from t))`
-        /// requires `children` to keep projection node. If the row
-        /// contains only constants (i.e. `(1, 2)`), then `children`
-        /// should be empty.
-        children: Vec<NodeId>,
-    },
-    Limit {
-        /// Output tuple.
-        output: NodeId,
-        // The limit value constant that comes after LIMIT keyword.
-        limit: u64,
-        /// Select statement that is being limited.
-        /// Note that it can be a complex statement, like SELECT .. UNION ALL SELECT .. LIMIT 100,
-        /// in that case limit is applied to the result of union.
-        child: NodeId,
-    },
-}
-
-#[allow(dead_code)]
-impl Relational {
-    /// Gets an immutable id of the output tuple node of the plan's arena.
-    #[must_use]
-    pub fn output(&self) -> NodeId {
-        match self {
-            Relational::ScanCte { output, .. }
-            | Relational::Except { output, .. }
-            | Relational::GroupBy { output, .. }
-            | Relational::OrderBy { output, .. }
-            | Relational::Having { output, .. }
-            | Relational::Update { output, .. }
-            | Relational::Join { output, .. }
-            | Relational::Delete { output, .. }
-            | Relational::Insert { output, .. }
-            | Relational::Intersect { output, .. }
-            | Relational::Motion { output, .. }
-            | Relational::Projection { output, .. }
-            | Relational::ScanRelation { output, .. }
-            | Relational::ScanSubQuery { output, .. }
-            | Relational::Selection { output, .. }
-            | Relational::Union { output, .. }
-            | Relational::UnionAll { output, .. }
-            | Relational::Values { output, .. }
-            | Relational::ValuesRow { output, .. }
-            | Relational::Limit { output, .. } => *output,
-        }
-    }
-
-    /// Gets an immutable reference to the output tuple node id.
-    #[must_use]
-    pub fn mut_output(&mut self) -> &mut NodeId {
-        match self {
-            Relational::ScanCte { output, .. }
-            | Relational::Except { output, .. }
-            | Relational::GroupBy { output, .. }
-            | Relational::OrderBy { output, .. }
-            | Relational::Update { output, .. }
-            | Relational::Having { output, .. }
-            | Relational::Join { output, .. }
-            | Relational::Delete { output, .. }
-            | Relational::Insert { output, .. }
-            | Relational::Intersect { output, .. }
-            | Relational::Motion { output, .. }
-            | Relational::Projection { output, .. }
-            | Relational::ScanRelation { output, .. }
-            | Relational::ScanSubQuery { output, .. }
-            | Relational::Selection { output, .. }
-            | Relational::Union { output, .. }
-            | Relational::UnionAll { output, .. }
-            | Relational::Values { output, .. }
-            | Relational::ValuesRow { output, .. }
-            | Relational::Limit { output, .. } => output,
-        }
-    }
-
-    // Gets an immutable reference to the children nodes.
-    #[must_use]
-    pub fn children(&self) -> Children<'_> {
-        match self {
-            Relational::OrderBy { child, .. }
-            | Relational::ScanCte { child, .. }
-            | Relational::Limit { child, .. } => Children::Single(child),
-            Relational::Except { left, right, .. }
-            | Relational::Intersect { left, right, .. }
-            | Relational::UnionAll { left, right, .. }
-            | Relational::Union { left, right, .. } => Children::Couple(left, right),
-            Relational::GroupBy { children, .. }
-            | Relational::Update { children, .. }
-            | Relational::Join { children, .. }
-            | Relational::Having { children, .. }
-            | Relational::Delete { children, .. }
-            | Relational::Insert { children, .. }
-            | Relational::Motion { children, .. }
-            | Relational::Projection { children, .. }
-            | Relational::ScanSubQuery { children, .. }
-            | Relational::Selection { children, .. }
-            | Relational::ValuesRow { children, .. }
-            | Relational::Values { children, .. } => Children::Many(children),
-            Relational::ScanRelation { .. } => Children::None,
-        }
-    }
-
-    // Gets a mutable reference to the children nodes.
-    #[must_use]
-    pub fn mut_children(&mut self) -> MutChildren<'_> {
-        // return MutChildren { node: self };
-        match self {
-            Relational::OrderBy { child, .. }
-            | Relational::ScanCte { child, .. }
-            | Relational::Limit { child, .. } => MutChildren::Single(child),
-            Relational::Except { left, right, .. }
-            | Relational::Intersect { left, right, .. }
-            | Relational::UnionAll { left, right, .. }
-            | Relational::Union { left, right, .. } => MutChildren::Couple(left, right),
-            Relational::GroupBy {
-                ref mut children, ..
-            }
-            | Relational::Update {
-                ref mut children, ..
-            }
-            | Relational::Having {
-                ref mut children, ..
-            }
-            | Relational::Join {
-                ref mut children, ..
-            }
-            | Relational::Delete {
-                ref mut children, ..
-            }
-            | Relational::Insert {
-                ref mut children, ..
-            }
-            | Relational::Motion {
-                ref mut children, ..
-            }
-            | Relational::Projection {
-                ref mut children, ..
-            }
-            | Relational::ScanSubQuery {
-                ref mut children, ..
-            }
-            | Relational::Selection {
-                ref mut children, ..
-            }
-            | Relational::ValuesRow {
-                ref mut children, ..
-            }
-            | Relational::Values {
-                ref mut children, ..
-            } => MutChildren::Many(children),
-            Relational::ScanRelation { .. } => MutChildren::None,
-        }
-    }
-
-    /// Checks if the node is deletion.
-    #[must_use]
-    pub fn is_delete(&self) -> bool {
-        matches!(self, Relational::Delete { .. })
-    }
-    /// Checks if the node is an insertion.
-    #[must_use]
-    pub fn is_insert(&self) -> bool {
-        matches!(self, Relational::Insert { .. })
-    }
-
-    /// Checks if the node is dml node
-    #[must_use]
-    pub fn is_dml(&self) -> bool {
-        matches!(
-            self,
-            Relational::Insert { .. } | Relational::Update { .. } | Relational::Delete { .. }
-        )
-    }
-
-    /// Checks that the node is a motion.
-    #[must_use]
-    pub fn is_motion(&self) -> bool {
-        matches!(self, &Relational::Motion { .. })
-    }
-
-    /// Checks that the node is a sub-query or CTE scan.
-    #[must_use]
-    pub fn is_subquery_or_cte(&self) -> bool {
-        matches!(
-            self,
-            &Relational::ScanSubQuery { .. } | &Relational::ScanCte { .. }
-        )
-    }
-
-    /// Sets new children to relational node.
-    ///
-    /// # Panics
-    /// - wrong number of children for the given node
-    pub fn set_children(&mut self, children: Vec<NodeId>) {
-        match self {
-            Relational::Join {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Delete {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Update {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Insert {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Motion {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Projection {
-                children: ref mut old,
-                ..
-            }
-            | Relational::ScanSubQuery {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Selection {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Values {
-                children: ref mut old,
-                ..
-            }
-            | Relational::GroupBy {
-                children: ref mut old,
-                ..
-            }
-            | Relational::Having {
-                children: ref mut old,
-                ..
-            }
-            | Relational::ValuesRow {
-                children: ref mut old,
-                ..
-            } => {
-                *old = children;
-            }
-            Relational::Except { left, right, .. }
-            | Relational::UnionAll { left, right, .. }
-            | Relational::Intersect { left, right, .. }
-            | Relational::Union { left, right, .. } => {
-                if children.len() != 2 {
-                    unreachable!("Node has only two children!");
-                }
-                *left = children[0];
-                *right = children[1];
-            }
-            Relational::OrderBy { ref mut child, .. } => {
-                if children.len() != 1 {
-                    unreachable!("ORDER BY may have only a single relational child");
-                }
-                // It is safe to unwrap here, because the length is already checked above.
-                *child = children[0];
-            }
-            Relational::ScanCte { ref mut child, .. } => {
-                if children.len() != 1 {
-                    unreachable!("CTE may have only a single relational child");
-                }
-                // It is safe to unwrap here, because the length is already checked above.
-                *child = children[0];
-            }
-            Relational::Limit { ref mut child, .. } => {
-                if children.len() != 1 {
-                    unreachable!("LIMIT may have only a single relational child");
-                }
-                // It is safe to unwrap here, because the length is already checked above.
-                *child = children[0];
-            }
-            Relational::ScanRelation { .. } => {
-                assert!(children.is_empty(), "scan must have no children!");
-            }
-        }
-    }
-
-    /// Get relational Scan name that given `output_alias_position` (`Expression::Alias`)
-    /// references to.
-    ///
-    /// # Errors
-    /// - plan tree is invalid (failed to retrieve child nodes)
-    pub fn scan_name<'n>(
-        &'n self,
-        plan: &'n Plan,
-        output_alias_position: usize,
-    ) -> Result<Option<&'n str>, SbroadError> {
-        match self {
-            Relational::Insert { relation, .. } | Relational::Delete { relation, .. } => {
-                Ok(Some(relation.as_str()))
-            }
-            Relational::ScanRelation {
-                alias, relation, ..
-            } => Ok(alias.as_deref().or(Some(relation.as_str()))),
-            Relational::Projection { .. }
-            | Relational::GroupBy { .. }
-            | Relational::OrderBy { .. }
-            | Relational::Intersect { .. }
-            | Relational::Having { .. }
-            | Relational::Selection { .. }
-            | Relational::Update { .. }
-            | Relational::Join { .. } => {
-                let output_row = plan.get_expression_node(self.output())?;
-                let list = output_row.get_row_list()?;
-                let col_id = *list.get(output_alias_position).ok_or_else(|| {
-                    SbroadError::NotFound(
-                        Entity::Column,
-                        format_smolstr!(
-                            "at position {output_alias_position} of Row, {self:?}, {output_row:?}"
-                        ),
-                    )
-                })?;
-                let col_node = plan.get_expression_node(col_id)?;
-                if let Expression::Alias { child, .. } = col_node {
-                    let child_node = plan.get_expression_node(*child)?;
-                    if let Expression::Reference { position: pos, .. } = child_node {
-                        let rel_id = *plan.get_relational_from_reference_node(*child)?;
-                        let rel_node = plan.get_relation_node(rel_id)?;
-                        if rel_node == self {
-                            return Err(SbroadError::DuplicatedValue(format_smolstr!(
-                                "Reference to the same node {rel_node:?} at position {output_alias_position}"
-                            )));
-                        }
-                        return rel_node.scan_name(plan, *pos);
-                    }
-                } else {
-                    return Err(SbroadError::Invalid(
-                        Entity::Expression,
-                        Some("expected an alias in the output row".into()),
-                    ));
-                }
-                Ok(None)
-            }
-            Relational::ScanCte { alias, .. } => Ok(Some(alias)),
-            Relational::ScanSubQuery { alias, .. } | Relational::Motion { alias, .. } => {
-                if let Some(name) = alias.as_ref() {
-                    if !name.is_empty() {
-                        return Ok(alias.as_deref());
-                    }
-                }
-                Ok(None)
-            }
-            Relational::Except { .. }
-            | Relational::Union { .. }
-            | Relational::UnionAll { .. }
-            | Relational::Values { .. }
-            | Relational::ValuesRow { .. }
-            | Relational::Limit { .. } => Ok(None),
-        }
-    }
-
-    #[must_use]
-    pub fn name(&self) -> &str {
-        match self {
-            Relational::Except { .. } => "Except",
-            Relational::Delete { .. } => "Delete",
-            Relational::Insert { .. } => "Insert",
-            Relational::Intersect { .. } => "Intersect",
-            Relational::Update { .. } => "Update",
-            Relational::Join { .. } => "Join",
-            Relational::Motion { .. } => "Motion",
-            Relational::Projection { .. } => "Projection",
-            Relational::ScanCte { .. } => "CTE",
-            Relational::ScanRelation { .. } => "Scan",
-            Relational::ScanSubQuery { .. } => "Subquery",
-            Relational::Selection { .. } => "Selection",
-            Relational::GroupBy { .. } => "GroupBy",
-            Relational::OrderBy { .. } => "OrderBy",
-            Relational::Having { .. } => "Having",
-            Relational::Union { .. } => "Union",
-            Relational::UnionAll { .. } => "UnionAll",
-            Relational::Values { .. } => "Values",
-            Relational::ValuesRow { .. } => "ValuesRow",
-            Relational::Limit { .. } => "Limit",
-        }
-    }
-
-    /// Sets new scan name to relational node.
-    ///
-    /// # Errors
-    /// - relational node is not a scan.
-    ///
-    /// # Panics
-    /// - CTE must have a name.
-    pub fn set_scan_name(&mut self, name: Option<SmolStr>) -> Result<(), SbroadError> {
-        match self {
-            Relational::ScanRelation { ref mut alias, .. }
-            | Relational::ScanSubQuery { ref mut alias, .. } => {
-                *alias = name;
-                Ok(())
-            }
-            Relational::ScanCte { ref mut alias, .. } => {
-                let name = name.expect("CTE must have a name");
-                *alias = name;
-                Ok(())
-            }
-            _ => Err(SbroadError::Invalid(
-                Entity::Relational,
-                Some("Relational node is not a Scan.".into()),
-            )),
-        }
-    }
-}
-
 impl Plan {
     /// Add relational node to plan arena and update shard columns info.
     ///
     /// # Errors
     /// - failed to oupdate shard columns info due to invalid plan subtree
-    pub fn add_relational(&mut self, node: Relational) -> Result<NodeId, SbroadError> {
-        let rel_id = self.nodes.push(Node::Relational(node));
+    pub fn add_relational(&mut self, node: SizeNode) -> Result<NodeId, SbroadError> {
+        let rel_id = self.nodes.push(node);
         let mut context = self.context_mut();
         context.shard_col_info.update_node(rel_id, self)?;
         Ok(rel_id)
@@ -922,12 +338,12 @@ impl Plan {
     /// - child id pointes to non-existing or non-relational node.
     pub fn add_delete(&mut self, table: SmolStr, child_id: NodeId) -> Result<NodeId, SbroadError> {
         let output = self.add_row_for_output(child_id, &[], true)?;
-        let delete = Relational::Delete {
+        let delete = Delete {
             relation: table,
             children: vec![child_id],
             output,
         };
-        let delete_id = self.add_relational(delete)?;
+        let delete_id = self.add_relational(delete.into())?;
         self.replace_parent_in_subtree(output, None, Some(delete_id))?;
         Ok(delete_id)
     }
@@ -956,13 +372,13 @@ impl Plan {
         }
 
         let output = self.add_row_for_union_except(left, right)?;
-        let except = Relational::Except {
+        let except = Except {
             left,
             right,
             output,
         };
 
-        let except_id = self.add_relational(except)?;
+        let except_id = self.add_relational(except.into())?;
         self.replace_parent_in_subtree(output, None, Some(except_id))?;
         Ok(except_id)
     }
@@ -1056,7 +472,7 @@ impl Plan {
                 )
             })?;
             let col_type = col.r#type.clone();
-            let node = Expression::Reference {
+            let node = Reference {
                 // It will be updated using `replace_parent_in_subtree`
                 // in the end of the function.
                 parent: None,
@@ -1064,7 +480,7 @@ impl Plan {
                 position: output_pos,
                 col_type,
             };
-            let id = plan.nodes.push(Node::Expression(node));
+            let id = plan.nodes.push(node.into());
             Ok(id)
         }
 
@@ -1201,22 +617,25 @@ impl Plan {
         // it is assumed that any projection column always has an alias.
         for (pos, expr_id) in projection_cols.iter_mut().enumerate() {
             let alias = get_unnamed_column_alias(pos);
-            let alias_id = self.nodes.push(Node::Expression(Expression::Alias {
-                child: *expr_id,
-                name: alias,
-            }));
+            let alias_id = self.nodes.push(
+                Alias {
+                    child: *expr_id,
+                    name: alias,
+                }
+                .into(),
+            );
             *expr_id = alias_id;
         }
         let proj_output = self.nodes.add_row(projection_cols, None);
-        let proj_node = Relational::Projection {
+        let proj_node = Projection {
             children: vec![rel_child_id],
             output: proj_output,
             is_distinct: false,
         };
-        let proj_id = self.add_relational(proj_node)?;
+        let proj_id = self.add_relational(proj_node.into())?;
         self.replace_parent_in_subtree(proj_output, None, Some(proj_id))?;
         let upd_output = self.add_row_for_output(proj_id, &[], false)?;
-        let update_node = Relational::Update {
+        let update_node = Update {
             relation: relation.to_smolstr(),
             pk_positions: primary_key_positions,
             children: vec![proj_id],
@@ -1224,7 +643,7 @@ impl Plan {
             output: upd_output,
             strategy: update_kind,
         };
-        let update_id = self.add_relational(update_node)?;
+        let update_id = self.add_relational(update_node.into())?;
         self.replace_parent_in_subtree(upd_output, None, Some(update_id))?;
 
         Ok(update_id)
@@ -1280,7 +699,7 @@ impl Plan {
         };
         let child_rel = self.get_relation_node(child)?;
         let child_output = self.get_expression_node(child_rel.output())?;
-        let child_output_list_len = if let Expression::Row { list, .. } = child_output {
+        let child_output_list_len = if let Expression::Row(Row { list, .. }) = child_output {
             list.len()
         } else {
             return Err(SbroadError::Invalid(
@@ -1313,14 +732,14 @@ impl Plan {
             }
         };
         let output = self.nodes.add_row(refs, Some(dist));
-        let insert = Node::Relational(Relational::Insert {
+        let insert = Insert {
             relation: relation.into(),
             columns,
             children: vec![child],
             output,
             conflict_strategy,
-        });
-        let insert_id = self.nodes.push(insert);
+        };
+        let insert_id = self.nodes.push(insert.into());
         self.replace_parent_in_subtree(output, None, Some(insert_id))?;
         Ok(insert_id)
     }
@@ -1341,13 +760,13 @@ impl Plan {
             }
 
             let output_id = nodes.add_row(refs, None);
-            let scan = Relational::ScanRelation {
+            let scan = ScanRelation {
                 output: output_id,
                 relation: SmolStr::from(table),
                 alias: alias.map(SmolStr::from),
             };
 
-            let scan_id = self.add_relational(scan)?;
+            let scan_id = self.add_relational(scan.into())?;
             self.replace_parent_in_subtree(output_id, None, Some(scan_id))?;
             return Ok(scan_id);
         }
@@ -1387,9 +806,9 @@ impl Plan {
         let mut children: Vec<NodeId> = Vec::with_capacity(2);
         for (child, join_child) in &[(left, JoinChild::Outer), (right, JoinChild::Inner)] {
             let child_node = self.get_relation_node(*child)?;
-            if let Relational::ScanRelation {
+            if let Relational::ScanRelation(ScanRelation {
                 relation, alias, ..
-            } = child_node
+            }) = child_node
             {
                 // We'll need it later to update the condition expression (borrow checker).
                 let table = self.get_relation_or_error(relation)?;
@@ -1399,10 +818,7 @@ impl Plan {
                 // Update references to the sub-query's output in the condition.
                 let condition_nodes = {
                     let filter = |id| -> bool {
-                        matches!(
-                            self.get_expression_node(id),
-                            Ok(Expression::Reference { .. })
-                        )
+                        matches!(self.get_expression_node(id), Ok(Expression::Reference(_)))
                     };
                     let mut condition_tree = PostOrderWithFilter::with_capacity(
                         |node| self.nodes.expr_iter(node, false),
@@ -1420,9 +836,9 @@ impl Plan {
                 let mut refs = Vec::with_capacity(condition_nodes.len());
                 for LevelNode(_, id) in condition_nodes {
                     let expr = self.get_expression_node(id)?;
-                    if let Expression::Reference {
+                    if let Expression::Reference(Reference {
                         position, targets, ..
-                    } = expr
+                    }) = expr
                     {
                         if *targets == current_target {
                             if Some(*position) == sharding_column_pos {
@@ -1450,9 +866,9 @@ impl Plan {
                 if let Some(sharding_column_pos) = sharding_column_pos {
                     for ref_id in refs {
                         let expr = self.get_mut_expression_node(ref_id)?;
-                        if let Expression::Reference {
+                        if let MutExpression::Reference(Reference {
                             position, targets, ..
-                        } = expr
+                        }) = expr
                         {
                             if current_target == *targets && *position > sharding_column_pos {
                                 *position -= 1;
@@ -1466,14 +882,14 @@ impl Plan {
         }
         if let (Some(left_id), Some(right_id)) = (children.first(), children.get(1)) {
             let output = self.add_row_for_join(*left_id, *right_id)?;
-            let join = Relational::Join {
+            let join = Join {
                 children: vec![*left_id, *right_id],
                 condition,
                 output,
                 kind,
             };
 
-            let join_id = self.add_relational(join)?;
+            let join_id = self.add_relational(join.into())?;
             self.replace_parent_in_subtree(condition, None, Some(join_id))?;
             self.replace_parent_in_subtree(output, None, Some(join_id))?;
             return Ok(join_id);
@@ -1495,8 +911,9 @@ impl Plan {
         policy: &MotionPolicy,
         program: Program,
     ) -> Result<NodeId, SbroadError> {
-        let alias = if let Node::Relational(rel) = self.get_node(child_id)? {
-            rel.scan_name(self, 0)?.map(SmolStr::from)
+        let rel_node = self.get_node(child_id)?;
+        let alias = if let Node::Relational(_) = rel_node {
+            self.scan_name(child_id, 0)?.map(SmolStr::from)
         } else {
             return Err(SbroadError::Invalid(Entity::Relational, None));
         };
@@ -1527,9 +944,9 @@ impl Plan {
         }
 
         let child = self.get_relation_node(child_id)?;
-        let is_child_subquery = matches!(child, Relational::ScanSubQuery { .. });
+        let is_child_subquery = matches!(child, Relational::ScanSubQuery(_));
 
-        let motion = Relational::Motion {
+        let motion = Motion {
             alias,
             children: vec![child_id],
             policy: policy.clone(),
@@ -1537,7 +954,7 @@ impl Plan {
             output,
             is_child_subquery,
         };
-        let motion_id = self.add_relational(motion)?;
+        let motion_id = self.add_relational(motion.into())?;
         self.replace_parent_in_subtree(output, None, Some(motion_id))?;
         let mut context = self.context_mut();
         context
@@ -1563,13 +980,13 @@ impl Plan {
         needs_shard_col: bool,
     ) -> Result<NodeId, SbroadError> {
         let output = self.add_row_for_output(child, col_names, needs_shard_col)?;
-        let proj = Relational::Projection {
+        let proj = Projection {
             children: vec![child],
             output,
             is_distinct,
         };
 
-        let proj_id = self.add_relational(proj)?;
+        let proj_id = self.add_relational(proj.into())?;
         self.replace_parent_in_subtree(output, None, Some(proj_id))?;
         Ok(proj_id)
     }
@@ -1587,13 +1004,13 @@ impl Plan {
         is_distinct: bool,
     ) -> Result<NodeId, SbroadError> {
         let output = self.nodes.add_row(columns.to_vec(), None);
-        let proj = Relational::Projection {
+        let proj = Projection {
             children: vec![child],
             output,
             is_distinct,
         };
 
-        let proj_id = self.add_relational(proj)?;
+        let proj_id = self.add_relational(proj.into())?;
         self.replace_parent_in_subtree(output, None, Some(proj_id))?;
         Ok(proj_id)
     }
@@ -1625,13 +1042,13 @@ impl Plan {
         }
 
         let output = self.add_row_for_output(first_child, &[], true)?;
-        let select = Relational::Selection {
+        let select = Selection {
             children: children.into(),
             filter,
             output,
         };
 
-        let select_id = self.add_relational(select)?;
+        let select_id = self.add_relational(select.into())?;
         self.replace_parent_in_subtree(filter, None, Some(select_id))?;
         self.replace_parent_in_subtree(output, None, Some(select_id))?;
         Ok(select_id)
@@ -1673,13 +1090,13 @@ impl Plan {
         }
 
         let output = self.add_row_for_output(first_child, &[], true)?;
-        let having = Relational::Having {
+        let having = Having {
             children: children.into(),
             filter,
             output,
         };
 
-        let having_id = self.add_relational(having)?;
+        let having_id = self.add_relational(having.into())?;
         self.replace_parent_in_subtree(filter, None, Some(having_id))?;
         self.replace_parent_in_subtree(output, None, Some(having_id))?;
         Ok(having_id)
@@ -1699,13 +1116,13 @@ impl Plan {
         order_by_elements: Vec<OrderByElement>,
     ) -> Result<NodeId, SbroadError> {
         let output = self.add_row_for_output(child, &[], true)?;
-        let order_by = Relational::OrderBy {
+        let order_by = OrderBy {
             child,
             output,
             order_by_elements: order_by_elements.clone(),
         };
 
-        let plan_order_by_id = self.add_relational(order_by)?;
+        let plan_order_by_id = self.add_relational(order_by.into())?;
         for order_by_element in order_by_elements {
             if let OrderByElement {
                 entity: OrderByEntity::Expression { expr_id },
@@ -1733,13 +1150,13 @@ impl Plan {
         let name: Option<SmolStr> = alias.map(SmolStr::from);
 
         let output = self.add_row_for_output(child, &[], true)?;
-        let sq = Relational::ScanSubQuery {
+        let sq = ScanSubQuery {
             alias: name,
             children: vec![child],
             output,
         };
 
-        let sq_id = self.add_relational(sq)?;
+        let sq_id = self.add_relational(sq.into())?;
         self.replace_parent_in_subtree(output, None, Some(sq_id))?;
         Ok(sq_id)
     }
@@ -1795,7 +1212,7 @@ impl Plan {
                 let col_alias = self
                     .get_mut_expression_node(col_id)
                     .expect("column expression");
-                if let Expression::Alias { name, .. } = col_alias {
+                if let MutExpression::Alias(Alias { name, .. }) = col_alias {
                     *name = col_name;
                 } else {
                     panic!("Expected a row of aliases in the output tuple");
@@ -1806,12 +1223,12 @@ impl Plan {
         let output = self
             .add_row_for_output(child_id, &[], true)
             .expect("output row for CTE");
-        let cte = Relational::ScanCte {
+        let cte = ScanCte {
             alias,
             child: child_id,
             output,
         };
-        let cte_id = self.add_relational(cte)?;
+        let cte_id = self.add_relational(cte.into())?;
         Ok(cte_id)
     }
 
@@ -1844,18 +1261,20 @@ impl Plan {
         }
 
         let output = self.add_row_for_union_except(left, right)?;
-        let union_all = if remove_duplicates {
-            Relational::Union {
+        let union_all: SizeNode = if remove_duplicates {
+            Union {
                 left,
                 right,
                 output,
             }
+            .into()
         } else {
-            Relational::UnionAll {
+            UnionAll {
                 left,
                 right,
                 output,
             }
+            .into()
         };
 
         let union_id = self.add_relational(union_all)?;
@@ -1869,13 +1288,13 @@ impl Plan {
     /// - Row node is not of a row type
     pub fn add_limit(&mut self, select: NodeId, limit: u64) -> Result<NodeId, SbroadError> {
         let output = self.add_row_for_output(select, &[], true)?;
-        let limit = Relational::Limit {
+        let limit = Limit {
             output,
             limit,
             child: select,
         };
 
-        let limit_id = self.add_relational(limit)?;
+        let limit_id = self.add_relational(limit.into())?;
         self.replace_parent_in_subtree(output, None, Some(limit_id))?;
         Ok(limit_id)
     }
@@ -1902,12 +1321,12 @@ impl Plan {
         }
         let output = self.nodes.add_row(aliases, None);
 
-        let values_row = Relational::ValuesRow {
+        let values_row = ValuesRow {
             output,
             data: row_id,
             children: vec![],
         };
-        let values_row_id = self.add_relational(values_row)?;
+        let values_row_id = self.add_relational(values_row.into())?;
         self.replace_parent_in_subtree(row_id, None, Some(values_row_id))?;
         Ok(values_row_id)
     }
@@ -1933,7 +1352,8 @@ impl Plan {
             ));
         };
         let value_row_last = self.get_relation_node(last_id)?;
-        let last_output_id = if let Relational::ValuesRow { output, .. } = value_row_last {
+        let last_output_id = if let Relational::ValuesRow(ValuesRow { output, .. }) = value_row_last
+        {
             *output
         } else {
             return Err(SbroadError::UnexpectedNumberOfValues(
@@ -1941,11 +1361,11 @@ impl Plan {
             ));
         };
         let last_output = self.get_expression_node(last_output_id)?;
-        let names = if let Expression::Row { list, .. } = last_output {
+        let names = if let Expression::Row(Row { list, .. }) = last_output {
             let mut aliases: Vec<SmolStr> = Vec::with_capacity(list.len());
             for alias_id in list {
                 let alias = self.get_expression_node(*alias_id)?;
-                if let Expression::Alias { name, .. } = alias {
+                if let Expression::Alias(Alias { name, .. }) = alias {
                     aliases.push(name.clone());
                 } else {
                     return Err(SbroadError::Invalid(
@@ -1983,11 +1403,11 @@ impl Plan {
         }
         let output = self.nodes.add_row(aliases, None);
 
-        let values = Relational::Values {
+        let values = Values {
             output,
             children: value_rows,
         };
-        let values_id = self.add_relational(values)?;
+        let values_id = self.add_relational(values.into())?;
         self.replace_parent_in_subtree(output, None, Some(values_id))?;
         Ok(values_id)
     }
@@ -2012,7 +1432,7 @@ impl Plan {
     /// - any node in the output tuple is not `Expression::Alias`
     pub fn get_relational_aliases(&self, rel_id: NodeId) -> Result<Vec<SmolStr>, SbroadError> {
         let output = self.get_relational_output(rel_id)?;
-        if let Expression::Row { list, .. } = self.get_expression_node(output)? {
+        if let Expression::Row(Row { list, .. }) = self.get_expression_node(output)? {
             return list
                 .iter()
                 .map(|alias_id| {
@@ -2035,8 +1455,8 @@ impl Plan {
     /// # Errors
     /// - node is not relational
     pub fn get_relational_children(&self, rel_id: NodeId) -> Result<Children<'_>, SbroadError> {
-        if let Node::Relational(rel) = self.get_node(rel_id)? {
-            Ok(rel.children())
+        if let Node::Relational(_) = self.get_node(rel_id)? {
+            Ok(self.children(rel_id))
         } else {
             Err(SbroadError::Invalid(
                 Entity::Node,
@@ -2067,7 +1487,7 @@ impl Plan {
         }
         Err(SbroadError::NotFound(
             Entity::Relational,
-            format_smolstr!("with id ({rel_id:?})"),
+            format_smolstr!("with id ({rel_id})"),
         ))
     }
 
@@ -2086,7 +1506,7 @@ impl Plan {
                 return Err(SbroadError::Invalid(
                     Entity::Node,
                     Some(format_smolstr!(
-                        "expected Join, Having or Selection on id ({node_id:?})"
+                        "expected Join, Having or Selection on id ({node_id})"
                     )),
                 ))
             }
@@ -2095,29 +1515,96 @@ impl Plan {
     }
 
     /// Finds the parent of the given relational node.
-    ///
     /// # Panics
     /// # Errors
     /// - node is not relational
     /// - Plan has no top
     pub fn find_parent_rel(&self, target_id: NodeId) -> Result<Option<NodeId>, SbroadError> {
-        for (id, node) in self.nodes.iter().enumerate() {
-            if !matches!(node, Node::Relational(_)) {
+        for (id, _) in self.nodes.arena32.iter().enumerate() {
+            let parent_id = NodeId {
+                offset: u32::try_from(id).unwrap(),
+                arena_type: ArenaType::Arena32,
+            };
+
+            if !matches!(self.get_node(parent_id)?, Node::Relational(_)) {
+                continue;
+            }
+
+            for child_id in self.nodes.rel_iter(parent_id) {
+                if child_id == target_id {
+                    return Ok(Some(parent_id));
+                }
+            }
+        }
+
+        for (id, _) in self.nodes.arena64.iter().enumerate() {
+            let parent_id = NodeId {
+                offset: u32::try_from(id).unwrap(),
+                arena_type: ArenaType::Arena64,
+            };
+
+            if !matches!(self.get_node(parent_id)?, Node::Relational(_)) {
+                continue;
+            }
+
+            for child_id in self.nodes.rel_iter(parent_id) {
+                if child_id == target_id {
+                    return Ok(Some(parent_id));
+                }
+            }
+        }
+
+        for (id, _) in self.nodes.arena96.iter().enumerate() {
+            let parent_id = NodeId {
+                offset: u32::try_from(id).unwrap(),
+                arena_type: ArenaType::Arena96,
+            };
+
+            if !matches!(self.get_node(parent_id)?, Node::Relational(_)) {
+                continue;
+            }
+
+            for child_id in self.nodes.rel_iter(parent_id) {
+                if child_id == target_id {
+                    return Ok(Some(parent_id));
+                }
+            }
+        }
+
+        for (id, _) in self.nodes.arena136.iter().enumerate() {
+            let parent_id = NodeId {
+                offset: u32::try_from(id).unwrap(),
+                arena_type: ArenaType::Arena136,
+            };
+
+            if !matches!(self.get_node(parent_id)?, Node::Relational(_)) {
                 continue;
             }
 
-            let rel_id = NodeId {
+            for child_id in self.nodes.rel_iter(parent_id) {
+                if child_id == target_id {
+                    return Ok(Some(parent_id));
+                }
+            }
+        }
+
+        for (id, _) in self.nodes.arena224.iter().enumerate() {
+            let parent_id = NodeId {
                 offset: u32::try_from(id).unwrap(),
-                arena_type: ArenaType::Default,
+                arena_type: ArenaType::Arena224,
             };
 
-            // It will work with one arena but fails with many. Needs to be refactored.
-            for child_id in self.nodes.rel_iter(rel_id) {
+            if !matches!(self.get_node(parent_id)?, Node::Relational(_)) {
+                continue;
+            }
+
+            for child_id in self.nodes.rel_iter(parent_id) {
                 if child_id == target_id {
-                    return Ok(Some(rel_id));
+                    return Ok(Some(parent_id));
                 }
             }
         }
+
         Ok(None)
     }
 
@@ -2132,7 +1619,7 @@ impl Plan {
         old_child_id: NodeId,
         new_child_id: NodeId,
     ) -> Result<(), SbroadError> {
-        let node = self.get_mut_relation_node(parent_id)?;
+        let mut node = self.get_mut_relation_node(parent_id)?;
         let children = node.mut_children();
         for child_id in children {
             if *child_id == old_child_id {
@@ -2187,13 +1674,88 @@ impl Plan {
         rel_id: NodeId,
         children: Vec<NodeId>,
     ) -> Result<(), SbroadError> {
-        if let Some(Node::Relational(ref mut rel)) = self.nodes.get_mut(rel_id) {
+        if let MutNode::Relational(ref mut rel) = self.get_mut_node(rel_id)? {
             rel.set_children(children);
             return Ok(());
         }
         Err(SbroadError::Invalid(Entity::Relational, None))
     }
 
+    /// Get relational Scan name that given `output_alias_position` (`Expression::Alias`)
+    /// references to.
+    ///
+    /// # Errors
+    /// - plan tree is invalid (failed to retrieve child nodes)
+    pub fn scan_name(
+        &self,
+        id: NodeId,
+        output_alias_position: usize,
+    ) -> Result<Option<&str>, SbroadError> {
+        let node = self.get_relation_node(id)?;
+        match node {
+            Relational::Insert(Insert { relation, .. })
+            | Relational::Delete(Delete { relation, .. }) => Ok(Some(relation.as_str())),
+            Relational::ScanRelation(ScanRelation {
+                alias, relation, ..
+            }) => Ok(alias.as_deref().or(Some(relation.as_str()))),
+            Relational::Projection { .. }
+            | Relational::GroupBy { .. }
+            | Relational::OrderBy { .. }
+            | Relational::Intersect { .. }
+            | Relational::Having { .. }
+            | Relational::Selection { .. }
+            | Relational::Update { .. }
+            | Relational::Join { .. } => {
+                let output_row = self.get_expression_node(node.output())?;
+                let list = output_row.get_row_list()?;
+                let col_id = *list.get(output_alias_position).ok_or_else(|| {
+                    SbroadError::NotFound(
+                        Entity::Column,
+                        format_smolstr!(
+                            "at position {output_alias_position} of Row, {self:?}, {output_row:?}"
+                        ),
+                    )
+                })?;
+                let col_node = self.get_expression_node(col_id)?;
+                if let Expression::Alias(Alias { child, .. }) = col_node {
+                    let child_node = self.get_expression_node(*child)?;
+                    if let Expression::Reference(Reference { position: pos, .. }) = child_node {
+                        let rel_id = *self.get_relational_from_reference_node(*child)?;
+                        let rel_node = self.get_relation_node(rel_id)?;
+                        if rel_node == node {
+                            return Err(SbroadError::DuplicatedValue(format_smolstr!(
+                                "Reference to the same node {rel_node:?} at position {output_alias_position}"
+                            )));
+                        }
+                        return self.scan_name(rel_id, *pos);
+                    }
+                } else {
+                    return Err(SbroadError::Invalid(
+                        Entity::Expression,
+                        Some("expected an alias in the output row".into()),
+                    ));
+                }
+                Ok(None)
+            }
+            Relational::ScanCte(ScanCte { alias, .. }) => Ok(Some(alias)),
+            Relational::ScanSubQuery(ScanSubQuery { alias, .. })
+            | Relational::Motion(Motion { alias, .. }) => {
+                if let Some(name) = alias.as_ref() {
+                    if !name.is_empty() {
+                        return Ok(alias.as_deref());
+                    }
+                }
+                Ok(None)
+            }
+            Relational::Except { .. }
+            | Relational::Union { .. }
+            | Relational::UnionAll { .. }
+            | Relational::Values { .. }
+            | Relational::Limit { .. }
+            | Relational::ValuesRow { .. } => Ok(None),
+        }
+    }
+
     /// Checks if the node is an additional child of some relational node.
     /// We can use a simple node scan instead of the tree traversal as we are interested
     /// only in relational nodes that can't be unlinked from the tree by our transformations.
@@ -2203,16 +1765,15 @@ impl Plan {
     /// - Failed to get plan top
     /// - Node returned by the relational iterator is not relational (bug)
     pub fn is_additional_child(&self, node_id: NodeId) -> Result<bool, SbroadError> {
-        for node in &self.nodes {
+        for node in &self.nodes.arena64 {
             match node {
-                Node::Relational(
-                    Relational::Selection { children, .. } | Relational::Having { children, .. },
-                ) => {
+                Node64::Selection(Selection { children, .. })
+                | Node64::Having(Having { children, .. }) => {
                     if children.iter().skip(1).any(|&c| c == node_id) {
                         return Ok(true);
                     }
                 }
-                Node::Relational(Relational::Join { children, .. }) => {
+                Node64::Join(Join { children, .. }) => {
                     if children.iter().skip(2).any(|&c| c == node_id) {
                         return Ok(true);
                     }
@@ -2250,7 +1811,7 @@ impl Plan {
     /// - node is not motion
     pub fn get_motion_policy(&self, motion_id: NodeId) -> Result<&MotionPolicy, SbroadError> {
         let node = self.get_relation_node(motion_id)?;
-        if let Relational::Motion { policy, .. } = node {
+        if let Relational::Motion(Motion { policy, .. }) = node {
             return Ok(policy);
         }
         Err(SbroadError::Invalid(
@@ -2258,6 +1819,35 @@ impl Plan {
             Some(format_smolstr!("expected Motion, got: {node:?}")),
         ))
     }
+
+    // Gets an immutable reference to the children nodes.
+    /// # Panics
+    #[must_use]
+    pub fn children(&self, rel_id: NodeId) -> Children<'_> {
+        let node = self.get_relation_node(rel_id).unwrap();
+        match node {
+            Relational::Limit(Limit { child, .. })
+            | Relational::OrderBy(OrderBy { child, .. })
+            | Relational::ScanCte(ScanCte { child, .. }) => Children::Single(child),
+            Relational::Except(Except { left, right, .. })
+            | Relational::Intersect(Intersect { left, right, .. })
+            | Relational::UnionAll(UnionAll { left, right, .. })
+            | Relational::Union(Union { left, right, .. }) => Children::Couple(left, right),
+            Relational::GroupBy(GroupBy { children, .. })
+            | Relational::Update(Update { children, .. })
+            | Relational::Join(Join { children, .. })
+            | Relational::Having(Having { children, .. })
+            | Relational::Delete(Delete { children, .. })
+            | Relational::Insert(Insert { children, .. })
+            | Relational::Motion(Motion { children, .. })
+            | Relational::Projection(Projection { children, .. })
+            | Relational::ScanSubQuery(ScanSubQuery { children, .. })
+            | Relational::Selection(Selection { children, .. })
+            | Relational::ValuesRow(ValuesRow { children, .. })
+            | Relational::Values(Values { children, .. }) => Children::Many(children),
+            Relational::ScanRelation(_) => Children::None,
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/sbroad-core/src/ir/operator/tests.rs b/sbroad-core/src/ir/operator/tests.rs
index 6bc322422..f3a2f6859 100644
--- a/sbroad-core/src/ir/operator/tests.rs
+++ b/sbroad-core/src/ir/operator/tests.rs
@@ -7,7 +7,7 @@ use crate::ir::expression::ColumnWithScan;
 use crate::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type};
 use crate::ir::tests::{column_user_non_null, sharding_column};
 use crate::ir::value::Value;
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 
 use super::*;
 
@@ -31,12 +31,12 @@ fn scan_rel() {
     plan.add_rel(t);
 
     let scan_output = NodeId {
-        offset: 8,
-        arena_type: ArenaType::Default,
+        offset: 4,
+        arena_type: ArenaType::Arena64,
     };
     let scan_node = NodeId {
-        offset: 9,
-        arena_type: ArenaType::Default,
+        offset: 5,
+        arena_type: ArenaType::Arena64,
     };
 
     let scan_id = plan.add_scan("t", None).unwrap();
@@ -44,10 +44,11 @@ fn scan_rel() {
     plan.top = Some(scan_node);
 
     plan.set_distribution(scan_output).unwrap();
-    if let Node::Expression(row) = plan.get_node(scan_output).unwrap() {
+    let row = plan.get_node(scan_output).unwrap();
+    if let Node::Expression(expr) = row {
         let keys: HashSet<_, RepeatableState> = collection! { Key::new(vec![1, 0]) };
         assert_eq!(
-            row.distribution().unwrap(),
+            expr.distribution().unwrap(),
             &Distribution::Segment { keys: keys.into() }
         );
     } else {
@@ -84,15 +85,17 @@ fn projection() {
     );
 
     let mut test_node = NodeId {
-        offset: 1,
-        arena_type: ArenaType::Default,
+        offset: 0,
+        arena_type: ArenaType::Arena32,
     };
 
     // Expression node instead of relational one
     assert_eq!(
         SbroadError::Invalid(
             Entity::Node,
-            Some("node is not Relational type: Expression(Alias { name: \"a\", child: NodeId { offset: 0, arena_type: Default } })".into())
+            Some(
+                "node is not Relational type: Expression(Alias(Alias { name: \"a\", child: NodeId { offset: 0, arena_type: Arena64 } }))".into()
+            )
         ),
         plan.add_proj(test_node, &["a"], false, false).unwrap_err()
     );
@@ -101,10 +104,7 @@ fn projection() {
 
     // Try to build projection from the non-existing node
     assert_eq!(
-        SbroadError::NotFound(
-            Entity::Node,
-            "from Default arena with index 42".to_smolstr()
-        ),
+        SbroadError::NotFound(Entity::Node, "from Arena32 with index 42".to_smolstr()),
         plan.add_proj(test_node, &["a"], false, false).unwrap_err()
     );
 }
@@ -368,13 +368,13 @@ fn sub_query() {
 
     // Non-relational child node
     let a = NodeId {
-        offset: 1,
-        arena_type: ArenaType::Default,
+        offset: 0,
+        arena_type: ArenaType::Arena32,
     };
     assert_eq!(
         SbroadError::Invalid(
             Entity::Node,
-            Some("node is not Relational type: Expression(Alias { name: \"a\", child: NodeId { offset: 0, arena_type: Default } })".into())
+            Some("node is not Relational type: Expression(Alias(Alias { name: \"a\", child: NodeId { offset: 0, arena_type: Arena64 } }))".into())
         ),
         plan.add_sub_query(a, Some("sq")).unwrap_err()
     );
diff --git a/sbroad-core/src/ir/parameters.rs b/sbroad-core/src/ir/parameters.rs
index eb5e001fc..d1f5da754 100644
--- a/sbroad-core/src/ir/parameters.rs
+++ b/sbroad-core/src/ir/parameters.rs
@@ -3,12 +3,10 @@
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 
-use crate::ir::Node;
-
-use super::expression::NodeId;
+use crate::ir::node::{Node64, NodeId};
 
 #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
-pub struct Parameters(HashMap<NodeId, Node>);
+pub struct Parameters(HashMap<NodeId, Node64>);
 
 impl Default for Parameters {
     fn default() -> Self {
@@ -22,16 +20,16 @@ impl Parameters {
         Self(HashMap::new())
     }
 
-    pub fn insert(&mut self, index: NodeId, node: Node) {
+    pub fn insert(&mut self, index: NodeId, node: Node64) {
         self.0.insert(index, node);
     }
 
     #[must_use]
-    pub fn get(&self, index: NodeId) -> Option<&Node> {
+    pub fn get(&self, index: NodeId) -> Option<&Node64> {
         self.0.get(&index)
     }
 
-    pub fn drain(&mut self) -> HashMap<NodeId, Node> {
+    pub fn drain(&mut self) -> HashMap<NodeId, Node64> {
         std::mem::take(&mut self.0)
     }
 }
diff --git a/sbroad-core/src/ir/tests.rs b/sbroad-core/src/ir/tests.rs
index b807ac665..15166202d 100644
--- a/sbroad-core/src/ir/tests.rs
+++ b/sbroad-core/src/ir/tests.rs
@@ -67,7 +67,7 @@ fn get_node() {
 
     let scan_id = plan.add_scan("t", None).unwrap();
 
-    if let Node::Relational(Relational::ScanRelation { relation, .. }) =
+    if let Node::Relational(Relational::ScanRelation(ScanRelation { relation, .. })) =
         plan.get_node(scan_id).unwrap()
     {
         assert_eq!(relation, "t");
@@ -80,10 +80,10 @@ fn get_node() {
 fn get_node_oor() {
     let plan = Plan::default();
     assert_eq!(
-        SbroadError::NotFound(Entity::Node, "from Default arena with index 42".into()),
+        SbroadError::NotFound(Entity::Node, "from Arena32 with index 42".into()),
         plan.get_node(NodeId {
             offset: 42,
-            arena_type: ArenaType::Default
+            arena_type: ArenaType::Arena32
         })
         .unwrap_err()
     );
diff --git a/sbroad-core/src/ir/transformation.rs b/sbroad-core/src/ir/transformation.rs
index b7779f0e2..cf7e9142b 100644
--- a/sbroad-core/src/ir/transformation.rs
+++ b/sbroad-core/src/ir/transformation.rs
@@ -12,11 +12,15 @@ pub mod split_columns;
 
 use smol_str::format_smolstr;
 
-use super::expression::NodeId;
+use super::node::expression::{Expression, MutExpression};
+use super::node::relational::{MutRelational, Relational};
 use super::tree::traversal::{PostOrder, PostOrderWithFilter, EXPR_CAPACITY};
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::Expression;
-use crate::ir::operator::{Bool, Relational};
+use crate::ir::node::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, ExprInParentheses, Join, NodeId, Row, Selection,
+    StableFunction, Trim, UnaryExpr,
+};
+use crate::ir::operator::Bool;
 use crate::ir::{Node, Plan};
 use std::collections::HashMap;
 
@@ -142,12 +146,12 @@ impl Plan {
             let id = level_node.1;
             let rel = self.get_relation_node(id)?;
             let (old_tree_id, new_tree_id) = match rel {
-                Relational::Selection {
+                Relational::Selection(Selection {
                     filter: tree_id, ..
-                }
-                | Relational::Join {
+                })
+                | Relational::Join(Join {
                     condition: tree_id, ..
-                } => f(self, *tree_id)?,
+                }) => f(self, *tree_id)?,
                 _ => continue,
             };
             if old_tree_id != new_tree_id {
@@ -155,12 +159,12 @@ impl Plan {
             }
             let rel = self.get_mut_relation_node(id)?;
             match rel {
-                Relational::Selection {
+                MutRelational::Selection(Selection {
                     filter: tree_id, ..
-                }
-                | Relational::Join {
+                })
+                | MutRelational::Join(Join {
                     condition: tree_id, ..
-                } => {
+                }) => {
                     *tree_id = new_tree_id;
                 }
                 _ => continue,
@@ -187,15 +191,15 @@ impl Plan {
         // * That will contain transformed nodes as children
         let filter = |node_id: NodeId| -> bool {
             if let Ok(Node::Expression(
-                Expression::Bool { .. }
-                | Expression::ExprInParentheses { .. }
-                | Expression::Arithmetic { .. }
-                | Expression::Alias { .. }
-                | Expression::Row { .. }
-                | Expression::Cast { .. }
-                | Expression::Case { .. }
-                | Expression::StableFunction { .. }
-                | Expression::Unary { .. },
+                Expression::Bool(_)
+                | Expression::ExprInParentheses(_)
+                | Expression::Arithmetic(_)
+                | Expression::Alias(_)
+                | Expression::Row(_)
+                | Expression::Cast(_)
+                | Expression::Case(_)
+                | Expression::StableFunction(_)
+                | Expression::Unary(_),
             )) = self.get_node(node_id)
             {
                 return true;
@@ -213,7 +217,7 @@ impl Plan {
         for level_node in &nodes {
             let id = level_node.1;
             let expr = self.get_expression_node(id)?;
-            if let Expression::Bool { op, .. } = expr {
+            if let Expression::Bool(BoolExpr { op, .. }) = expr {
                 if ops.contains(op) || ops.is_empty() {
                     let (old_top_id, new_top_id) = f(self, id)?;
                     if old_top_id != new_top_id {
@@ -240,17 +244,17 @@ impl Plan {
                 // XXX: If you add a new expression type to the match, make sure to
                 // add it to the filter above.
                 match expr {
-                    Expression::Alias { child, .. }
-                    | Expression::ExprInParentheses { child, .. }
-                    | Expression::Cast { child, .. }
-                    | Expression::Unary { child, .. } => {
+                    MutExpression::Alias(Alias { child, .. })
+                    | MutExpression::ExprInParentheses(ExprInParentheses { child, .. })
+                    | MutExpression::Cast(Cast { child, .. })
+                    | MutExpression::Unary(UnaryExpr { child, .. }) => {
                         map.replace(child);
                     }
-                    Expression::Case {
+                    MutExpression::Case(Case {
                         search_expr,
                         when_blocks,
                         else_expr,
-                    } => {
+                    }) => {
                         if let Some(search_expr) = search_expr {
                             map.replace(search_expr);
                         }
@@ -262,29 +266,29 @@ impl Plan {
                             map.replace(else_expr);
                         }
                     }
-                    Expression::Bool { left, right, .. }
-                    | Expression::Arithmetic { left, right, .. } => {
+                    MutExpression::Bool(BoolExpr { left, right, .. })
+                    | MutExpression::Arithmetic(ArithmeticExpr { left, right, .. }) => {
                         map.replace(left);
                         map.replace(right);
                     }
-                    Expression::Trim {
+                    MutExpression::Trim(Trim {
                         pattern, target, ..
-                    } => {
+                    }) => {
                         if let Some(pattern) = pattern {
                             map.replace(pattern);
                         }
                         map.replace(target);
                     }
-                    Expression::Row { list, .. }
-                    | Expression::StableFunction { children: list, .. } => {
+                    MutExpression::Row(Row { list, .. })
+                    | MutExpression::StableFunction(StableFunction { children: list, .. }) => {
                         for id in list {
                             map.replace(id);
                         }
                     }
-                    Expression::Concat { .. }
-                    | Expression::Constant { .. }
-                    | Expression::Reference { .. }
-                    | Expression::CountAsterisk => {}
+                    MutExpression::Concat(_)
+                    | MutExpression::Constant(_)
+                    | MutExpression::Reference(_)
+                    | MutExpression::CountAsterisk(_) => {}
                 }
             }
             // Checks if the top node is a new node.
diff --git a/sbroad-core/src/ir/transformation/bool_in.rs b/sbroad-core/src/ir/transformation/bool_in.rs
index be1bb8c5b..9fa275082 100644
--- a/sbroad-core/src/ir/transformation/bool_in.rs
+++ b/sbroad-core/src/ir/transformation/bool_in.rs
@@ -11,7 +11,8 @@
 //! ```
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{BoolExpr, NodeId};
 use crate::ir::operator::Bool;
 use crate::ir::transformation::OldNewTopIdPair;
 use crate::ir::Plan;
@@ -36,12 +37,12 @@ impl Plan {
     fn in_to_or(&mut self, top_id: NodeId) -> Result<OldNewTopIdPair, SbroadError> {
         let top_expr = self.get_expression_node(top_id)?;
         let (left_id, right_id) = match top_expr {
-            Expression::Bool {
+            Expression::Bool(BoolExpr {
                 left,
                 op: Bool::In,
                 right,
                 ..
-            } => (*left, *right),
+            }) => (*left, *right),
             _ => {
                 return Err(SbroadError::Invalid(
                     Entity::Expression,
diff --git a/sbroad-core/src/ir/transformation/dnf.rs b/sbroad-core/src/ir/transformation/dnf.rs
index 0cc77cedd..0e4944b9b 100644
--- a/sbroad-core/src/ir/transformation/dnf.rs
+++ b/sbroad-core/src/ir/transformation/dnf.rs
@@ -71,10 +71,11 @@
 //! ```
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{BoolExpr, ExprInParentheses, Node32, NodeId};
 use crate::ir::operator::Bool;
 use crate::ir::transformation::OldNewTopIdPair;
-use crate::ir::{Node, Plan};
+use crate::ir::Plan;
 use crate::otm::child_span;
 use sbroad_proc::otm_child_span;
 use std::collections::VecDeque;
@@ -90,14 +91,16 @@ pub struct Chain {
 fn optionally_covered_and_or(expr_id: NodeId, plan: &Plan) -> Result<Option<NodeId>, SbroadError> {
     let expr = plan.get_expression_node(expr_id)?;
     let and_or = match expr {
-        Expression::Bool { op, .. } => {
+        Expression::Bool(BoolExpr { op, .. }) => {
             if matches!(op, Bool::And) || matches!(op, Bool::Or) {
                 Some(expr_id)
             } else {
                 None
             }
         }
-        Expression::ExprInParentheses { child } => optionally_covered_and_or(*child, plan)?,
+        Expression::ExprInParentheses(ExprInParentheses { child }) => {
+            optionally_covered_and_or(*child, plan)?
+        }
         _ => None,
     };
     Ok(and_or)
@@ -134,10 +137,10 @@ impl Chain {
     fn pop_back(&mut self, plan: &Plan) -> Result<Option<NodeId>, SbroadError> {
         if let Some(expr_id) = self.nodes.back() {
             let expr = plan.get_expression_node(*expr_id)?;
-            if let Expression::Bool {
+            if let Expression::Bool(BoolExpr {
                 op: Bool::And | Bool::Or,
                 ..
-            } = expr
+            }) = expr
             {
                 return Ok(self.nodes.pop_back());
             }
@@ -186,9 +189,9 @@ impl Plan {
     /// - If the expression tree is not a trivalent expression.
     /// - Failed to append node to the AND chain.
     pub fn get_dnf_chains(&self, top_id: NodeId) -> Result<VecDeque<Chain>, SbroadError> {
-        let capacity: usize = self.nodes.arena.iter().fold(0_usize, |acc, node| {
+        let capacity: usize = self.nodes.arena32.iter().fold(0_usize, |acc, node| {
             acc + match node {
-                Node::Expression(Expression::Bool {
+                Node32::Bool(BoolExpr {
                     op: Bool::And | Bool::Or,
                     ..
                 }) => 1,
@@ -208,9 +211,9 @@ impl Plan {
                 continue;
             };
             let expr = self.get_expression_node(expr_id)?;
-            if let Expression::Bool {
+            if let Expression::Bool(BoolExpr {
                 op, left, right, ..
-            } = expr
+            }) = expr
             {
                 match *op {
                     Bool::And => {
diff --git a/sbroad-core/src/ir/transformation/equality_propagation.rs b/sbroad-core/src/ir/transformation/equality_propagation.rs
index dc7132d93..b4917ad65 100644
--- a/sbroad-core/src/ir/transformation/equality_propagation.rs
+++ b/sbroad-core/src/ir/transformation/equality_propagation.rs
@@ -90,8 +90,9 @@
 //!    to the plan tree.
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
 use crate::ir::helpers::RepeatableState;
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{Constant, NodeId, Reference, Row};
 use crate::ir::operator::Bool;
 use crate::ir::relation::Type;
 use crate::ir::transformation::merge_tuples::Chain;
@@ -115,12 +116,12 @@ struct EqClassRef {
 
 impl EqClassRef {
     fn from_ref(expr: &Expression) -> Result<Self, SbroadError> {
-        if let Expression::Reference {
+        if let Expression::Reference(Reference {
             targets: expr_tgt,
             position: expr_pos,
             parent: expr_prt,
             col_type: expr_type,
-        } = expr
+        }) = expr
         {
             return Ok(EqClassRef {
                 targets: expr_tgt.clone(),
@@ -157,7 +158,7 @@ impl Eq for EqClassConst {}
 
 impl EqClassConst {
     fn from_const(expr: &Expression) -> Result<Self, SbroadError> {
-        if let Expression::Constant { value: expr_value } = expr {
+        if let Expression::Constant(Constant { value: expr_value }) = expr {
             return Ok(EqClassConst {
                 value: expr_value.clone(),
             });
@@ -251,7 +252,7 @@ impl EqClassChain {
         // If one of the sides doesn't satisfy self equivalence, produce
         // a new equality class.
         for class in &mut self.list {
-            if (class.set.get(left).is_some() || class.set.get(right).is_some())
+            if (class.set.contains(left) || class.set.contains(right))
                 && left.is_self_equivalent()
                 && right.is_self_equivalent()
             {
@@ -281,14 +282,14 @@ impl EqClassChain {
     /// as `NULL != NULL` (the result is "NULL" itself).
     fn merge(&self) -> Self {
         let mut result = EqClassChain::new();
-        result.pairs = self.pairs.clone();
+        result.pairs.clone_from(&self.pairs);
 
         // A set of indexes of the equality classes in the chain
         // that contain common elements and should not be reinspected.
         let mut matched: HashSet<usize> = HashSet::new();
 
         for i in 0..self.list.len() {
-            if matched.get(&i).is_some() {
+            if matched.contains(&i) {
                 continue;
             }
 
@@ -297,7 +298,7 @@ impl EqClassChain {
                 matched.insert(i);
 
                 for j in i..self.list.len() {
-                    if matched.get(&j).is_some() {
+                    if matched.contains(&j) {
                         continue;
                     }
 
@@ -333,7 +334,7 @@ impl EqClassChain {
 
     fn subtract_pairs(&self) -> Self {
         let mut result = EqClassChain::new();
-        result.pairs = self.pairs.clone();
+        result.pairs.clone_from(&self.pairs);
 
         for class in &self.list {
             let ec_ref = class.ref_copy();
@@ -484,13 +485,11 @@ impl Plan {
     fn try_to_eq_class_expr(&self, expr_id: NodeId) -> Result<EqClassExpr, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
-            Expression::Constant { .. } => {
-                Ok(EqClassExpr::EqClassConst(EqClassConst::from_const(expr)?))
+            Expression::Constant(_) => {
+                Ok(EqClassExpr::EqClassConst(EqClassConst::from_const(&expr)?))
             }
-            Expression::Reference { .. } => {
-                Ok(EqClassExpr::EqClassRef(EqClassRef::from_ref(expr)?))
-            }
-            Expression::Row { list, .. } => {
+            Expression::Reference(_) => Ok(EqClassExpr::EqClassRef(EqClassRef::from_ref(&expr)?)),
+            Expression::Row(Row { list, .. }) => {
                 if let (Some(col_id), None) = (list.first(), list.get(1)) {
                     self.try_to_eq_class_expr(*col_id)
                 } else {
diff --git a/sbroad-core/src/ir/transformation/equality_propagation/tests.rs b/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
index 83f6bfa30..696ce4b30 100644
--- a/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
+++ b/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
@@ -18,7 +18,7 @@ fn equality_propagation1() {
             "{} {} {}",
             r#"SELECT "t"."a" FROM "t""#,
             r#"WHERE ("t"."c") = (?) and ("t"."a") = (?) and ("t"."b") = (?)"#,
-            r#"and ("t"."a") = ("t"."c") or ("t"."d") = (?)"#,
+            r#"and ("t"."c") = ("t"."a") or ("t"."d") = (?)"#,
         ),
         vec![
             Value::from(1_u64),
@@ -80,7 +80,7 @@ fn equality_propagation4() {
             "{} {} {}",
             r#"SELECT "t"."a" FROM "t""#,
             r#"WHERE ("t"."b") = (?) and ("t"."a") = (?) and ("t"."a") = (?)"#,
-            r#"and ("t"."b") = (?) and ("t"."a") = ("t"."b")"#,
+            r#"and ("t"."b") = (?) and ("t"."b") = ("t"."a")"#,
         ),
         vec![
             Value::from(1_u64),
@@ -107,8 +107,8 @@ fn equality_propagation5() {
             r#"SELECT "t"."a" FROM "t""#,
             r#"WHERE ("t"."d") = (?) and ("t"."c") = (?)"#,
             r#"and ("t"."a") = (?) and ("t"."b") = (?)"#,
-            r#"and ("t"."a") = ("t"."b") and ("t"."b") = ("t"."c")"#,
-            r#"and ("t"."c") = ("t"."d")"#,
+            r#"and ("t"."b") = ("t"."c") and ("t"."c") = ("t"."d")"#,
+            r#"and ("t"."d") = ("t"."a")"#,
         ),
         vec![
             Value::from(1_u64),
diff --git a/sbroad-core/src/ir/transformation/merge_tuples.rs b/sbroad-core/src/ir/transformation/merge_tuples.rs
index 30f76a9de..2aa1bf30a 100644
--- a/sbroad-core/src/ir/transformation/merge_tuples.rs
+++ b/sbroad-core/src/ir/transformation/merge_tuples.rs
@@ -11,8 +11,9 @@
 //! ```
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
 use crate::ir::helpers::RepeatableState;
+use crate::ir::node::expression::{Expression, MutExpression};
+use crate::ir::node::{Alias, ArithmeticExpr, BoolExpr, NodeId, Row};
 use crate::ir::operator::Bool;
 use crate::ir::transformation::OldNewTopIdPair;
 use crate::ir::tree::traversal::BreadthFirst;
@@ -69,7 +70,7 @@ impl Chain {
     /// - There is something wrong with our sub-queries.
     pub fn insert(&mut self, plan: &mut Plan, expr_id: NodeId) -> Result<(), SbroadError> {
         let bool_expr = plan.get_expression_node(expr_id)?;
-        if let Expression::Bool { left, op, right } = bool_expr {
+        if let Expression::Bool(BoolExpr { left, op, right }) = bool_expr {
             if let Bool::And | Bool::Or = op {
                 // We don't expect nested AND/OR expressions in DNF.
                 return Err(SbroadError::Unsupported(
@@ -89,12 +90,12 @@ impl Chain {
                         _ => (*left, *right, op.clone()),
                     };
 
-                if let Ok(Expression::Arithmetic { .. }) = plan.get_expression_node(left_id) {
+                if let Ok(Expression::Arithmetic(_)) = plan.get_expression_node(left_id) {
                     self.other.push(expr_id);
                     return Ok(());
                 }
 
-                if let Ok(Expression::Arithmetic { .. }) = plan.get_expression_node(right_id) {
+                if let Ok(Expression::Arithmetic(_)) = plan.get_expression_node(right_id) {
                     self.other.push(expr_id);
                     return Ok(());
                 }
@@ -208,7 +209,7 @@ impl Plan {
     fn get_columns_or_self(&self, expr_id: NodeId) -> Result<Vec<NodeId>, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         match expr {
-            Expression::Row { list, .. } => Ok(list.clone()),
+            Expression::Row(Row { list, .. }) => Ok(list.clone()),
             _ => Ok(vec![expr_id]),
         }
     }
@@ -242,21 +243,21 @@ impl Plan {
             let mut nodes_for_chain: Vec<NodeId> = Vec::with_capacity(nodes_and.len());
             for and_id in nodes_and {
                 let expr = self.get_expression_node(and_id)?;
-                if let Expression::Bool {
+                if let Expression::Bool(BoolExpr {
                     left,
                     op: Bool::And,
                     right,
                     ..
-                } = expr
+                }) = expr
                 {
                     let children = vec![*left, *right];
                     for child_id in children {
                         visited.insert(child_id);
                         let child_expr = self.get_expression_node(child_id)?;
-                        if let Expression::Bool {
+                        if let Expression::Bool(BoolExpr {
                             op: Bool::And | Bool::Or,
                             ..
-                        } = child_expr
+                        }) = child_expr
                         {
                             continue;
                         }
@@ -306,15 +307,15 @@ impl Plan {
         for id in nodes {
             let expr = self.get_expression_node(id)?;
             match expr {
-                Expression::Alias { child, .. } => {
+                Expression::Alias(Alias { child, .. }) => {
                     let chain = chains.get(child);
                     if let Some(chain) = chain {
                         let new_child_id = f_to_plan(chain, self)?;
                         let expr_mut = self.get_mut_expression_node(id)?;
-                        if let Expression::Alias {
+                        if let MutExpression::Alias(Alias {
                             child: ref mut child_id,
                             ..
-                        } = expr_mut
+                        }) = expr_mut
                         {
                             *child_id = new_child_id;
                         } else {
@@ -325,18 +326,18 @@ impl Plan {
                         }
                     }
                 }
-                Expression::Bool { left, right, .. } => {
+                Expression::Bool(BoolExpr { left, right, .. }) => {
                     let children = [*left, *right];
                     for (pos, child) in children.iter().enumerate() {
                         let chain = chains.get(child);
                         if let Some(chain) = chain {
                             let new_child_id = f_to_plan(chain, self)?;
                             let expr_mut = self.get_mut_expression_node(id)?;
-                            if let Expression::Bool {
+                            if let MutExpression::Bool(BoolExpr {
                                 left: ref mut left_id,
                                 right: ref mut right_id,
                                 ..
-                            } = expr_mut
+                            }) = expr_mut
                             {
                                 if pos == 0 {
                                     *left_id = new_child_id;
@@ -354,18 +355,18 @@ impl Plan {
                         }
                     }
                 }
-                Expression::Arithmetic { left, right, .. } => {
+                Expression::Arithmetic(ArithmeticExpr { left, right, .. }) => {
                     let children = [*left, *right];
                     for (pos, child) in children.iter().enumerate() {
                         let chain = chains.get(child);
                         if let Some(chain) = chain {
                             let new_child_id = f_to_plan(chain, self)?;
                             let expr_mut = self.get_mut_expression_node(id)?;
-                            if let Expression::Arithmetic {
+                            if let MutExpression::Arithmetic(ArithmeticExpr {
                                 left: ref mut left_id,
                                 right: ref mut right_id,
                                 ..
-                            } = expr_mut
+                            }) = expr_mut
                             {
                                 if pos == 0 {
                                     *left_id = new_child_id;
@@ -383,14 +384,14 @@ impl Plan {
                         }
                     }
                 }
-                Expression::Row { list, .. } => {
+                Expression::Row(Row { list, .. }) => {
                     let children = list.clone();
                     for (pos, child) in children.iter().enumerate() {
                         let chain = chains.get(child);
                         if let Some(chain) = chain {
                             let new_child_id = f_to_plan(chain, self)?;
                             let expr_mut = self.get_mut_expression_node(id)?;
-                            if let Expression::Row { ref mut list, .. } = expr_mut {
+                            if let MutExpression::Row(Row { ref mut list, .. }) = expr_mut {
                                 if let Some(child_id) = list.get_mut(pos) {
                                     *child_id = new_child_id;
                                 } else {
diff --git a/sbroad-core/src/ir/transformation/not_push_down.rs b/sbroad-core/src/ir/transformation/not_push_down.rs
index 06a672118..04ae44833 100644
--- a/sbroad-core/src/ir/transformation/not_push_down.rs
+++ b/sbroad-core/src/ir/transformation/not_push_down.rs
@@ -5,7 +5,8 @@
 //! * To:   `select * from "t" where "a" = 1 and "b" = 2`
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::{Expression, MutExpression};
+use crate::ir::node::{BoolExpr, Constant, ExprInParentheses, NodeId, Row, UnaryExpr};
 use crate::ir::operator::{Bool, Unary};
 use crate::ir::transformation::{OldNewExpressionMap, OldNewTopIdPair};
 use crate::ir::tree::traversal::{PostOrderWithFilter, EXPR_CAPACITY};
@@ -55,7 +56,6 @@ fn call_expr_tree_not_push_down(
     let mut old_new_expression_map = OldNewExpressionMap::new();
     let new_top_id =
         plan.push_down_not_for_expression(top_id, NotState::Off, &mut old_new_expression_map)?;
-
     let (old_top_id, new_top_id) = if new_top_id == top_id && old_new_expression_map.is_empty() {
         (top_id, top_id)
     } else {
@@ -64,9 +64,7 @@ fn call_expr_tree_not_push_down(
             matches!(
                 plan.get_node(node_id),
                 Ok(Node::Expression(
-                    Expression::ExprInParentheses { .. }
-                        | Expression::Bool { .. }
-                        | Expression::Row { .. }
+                    Expression::ExprInParentheses(_) | Expression::Bool(_) | Expression::Row(_)
                 ))
             )
         };
@@ -82,14 +80,14 @@ fn call_expr_tree_not_push_down(
             let id = level_node.1;
             let expr = plan.get_mut_expression_node(id)?;
             match expr {
-                Expression::ExprInParentheses { child } => {
+                MutExpression::ExprInParentheses(ExprInParentheses { child }) => {
                     old_new_expression_map.replace(child);
                 }
-                Expression::Bool { left, right, .. } => {
+                MutExpression::Bool(BoolExpr { left, right, .. }) => {
                     old_new_expression_map.replace(left);
                     old_new_expression_map.replace(right);
                 }
-                Expression::Row { list, .. } => {
+                MutExpression::Row(Row { list, .. }) => {
                     for id in list {
                         old_new_expression_map.replace(id);
                     }
@@ -138,10 +136,10 @@ impl Plan {
         if let NotState::On { parent_not_op } = not_state {
             if let Some(parent_not_op) = parent_not_op {
                 let parent_not_expr = self.get_mut_expression_node(*parent_not_op)?;
-                if let Expression::Unary {
+                if let MutExpression::Unary(UnaryExpr {
                     op: Unary::Not,
                     child,
-                } = parent_not_expr
+                }) = parent_not_expr
                 {
                     *child = expr_id;
                     Ok(*parent_not_op)
@@ -173,7 +171,7 @@ impl Plan {
     ) -> Result<NodeId, SbroadError> {
         let expr = self.get_expression_node(expr_id)?;
         let new_expr_id = match expr {
-            Expression::ExprInParentheses { child } => {
+            Expression::ExprInParentheses(ExprInParentheses { child }) => {
                 // In case we have expression `true and not (true and false)` we would like to
                 // save parentheses over not child:
                 // `true and false or true` != `true and (false or true)`.
@@ -184,7 +182,7 @@ impl Plan {
                 }
                 expr_id
             }
-            Expression::Constant { value } => {
+            Expression::Constant(Constant { value }) => {
                 if let NotState::Off = not_state {
                     expr_id
                 } else {
@@ -205,7 +203,7 @@ impl Plan {
                     }
                 }
             }
-            Expression::Bool { op, left, right } => {
+            Expression::Bool(BoolExpr { op, left, right }) => {
                 let (remember_left, remember_right) = (*left, *right);
 
                 if let NotState::On { .. } = not_state {
@@ -235,10 +233,10 @@ impl Plan {
                     expr_id
                 }
             }
-            Expression::StableFunction { .. }
-            | Expression::Cast { .. }
-            | Expression::Reference { .. } => self.cover_with_not(expr_id, &not_state)?,
-            Expression::Row { list, .. } => {
+            Expression::StableFunction(_) | Expression::Cast(_) | Expression::Reference(_) => {
+                self.cover_with_not(expr_id, &not_state)?
+            }
+            Expression::Row(Row { list, .. }) => {
                 let list_len = list.len();
                 if list_len == 1 {
                     let child_id = *list.first().ok_or_else(|| {
@@ -256,7 +254,7 @@ impl Plan {
                     self.cover_with_not(expr_id, &not_state)?
                 }
             }
-            Expression::Unary { op, child } => match op {
+            Expression::Unary(UnaryExpr { op, child }) => match op {
                 Unary::Not => {
                     if let NotState::On { .. } = not_state {
                         self.push_down_not_for_expression(*child, NotState::Off, map)?
diff --git a/sbroad-core/src/ir/transformation/redistribution.rs b/sbroad-core/src/ir/transformation/redistribution.rs
index 1832013da..0085a99be 100644
--- a/sbroad-core/src/ir/transformation/redistribution.rs
+++ b/sbroad-core/src/ir/transformation/redistribution.rs
@@ -10,10 +10,16 @@ use crate::errors::{Action, Entity, SbroadError};
 use crate::frontend::sql::ir::SubtreeCloner;
 use crate::ir::api::children::Children;
 use crate::ir::distribution::{Distribution, Key, KeySet};
-use crate::ir::expression::Expression;
-use crate::ir::expression::{ColumnPositionMap, NodeId};
-use crate::ir::operator::{Bool, JoinKind, Relational, Unary, UpdateStrategy};
-
+use crate::ir::expression::ColumnPositionMap;
+use crate::ir::node::expression::Expression;
+use crate::ir::node::relational::{RelOwned, Relational};
+use crate::ir::operator::{Bool, JoinKind, Unary, UpdateStrategy};
+
+use crate::ir::node::{
+    BoolExpr, Except, GroupBy, Having, Intersect, Join, Limit, NodeId, OrderBy, Projection,
+    Reference, ScanCte, ScanRelation, ScanSubQuery, Selection, UnaryExpr, Union, UnionAll, Update,
+    Values, ValuesRow,
+};
 use crate::ir::transformation::redistribution::eq_cols::EqualityCols;
 use crate::ir::tree::traversal::{
     BreadthFirst, LevelNode, PostOrder, PostOrderWithFilter, EXPR_CAPACITY, REL_CAPACITY,
@@ -161,9 +167,9 @@ struct BoolOp {
 
 impl BoolOp {
     fn from_expr(plan: &Plan, expr_id: NodeId) -> Result<Self, SbroadError> {
-        if let Expression::Bool {
+        if let Expression::Bool(BoolExpr {
             left, op, right, ..
-        } = plan.get_expression_node(expr_id)?
+        }) = plan.get_expression_node(expr_id)?
         {
             Ok(BoolOp {
                 left: *left,
@@ -277,7 +283,10 @@ impl Plan {
         let filter = |node_id: NodeId| -> bool {
             matches!(
                 self.get_node(node_id),
-                Ok(Node::Expression(Expression::Unary { op: Unary::Not, .. }))
+                Ok(Node::Expression(Expression::Unary(UnaryExpr {
+                    op: Unary::Not,
+                    ..
+                })))
             )
         };
         let mut post_tree = PostOrderWithFilter::with_capacity(
@@ -299,16 +308,16 @@ impl Plan {
     pub(crate) fn get_bool_nodes_with_row_children(&self, top: NodeId) -> Vec<LevelNode<NodeId>> {
         let filter = |node_id: NodeId| -> bool {
             // Append only booleans with row children.
-            if let Ok(Node::Expression(Expression::Bool { left, right, .. })) =
+            if let Ok(Node::Expression(Expression::Bool(BoolExpr { left, right, .. }))) =
                 self.get_node(node_id)
             {
                 let left_is_row = matches!(
                     self.get_node(*left),
-                    Ok(Node::Expression(Expression::Row { .. }))
+                    Ok(Node::Expression(Expression::Row(_)))
                 );
                 let right_is_row = matches!(
                     self.get_node(*right),
-                    Ok(Node::Expression(Expression::Row { .. }))
+                    Ok(Node::Expression(Expression::Row(_)))
                 );
                 if left_is_row && right_is_row {
                     return true;
@@ -335,10 +344,12 @@ impl Plan {
     pub(crate) fn get_unary_nodes_with_row_children(&self, top: NodeId) -> Vec<LevelNode<NodeId>> {
         let filter = |node_id: NodeId| -> bool {
             // Append only unaries with row children.
-            if let Ok(Node::Expression(Expression::Unary { child, .. })) = self.get_node(node_id) {
+            if let Ok(Node::Expression(Expression::Unary(UnaryExpr { child, .. }))) =
+                self.get_node(node_id)
+            {
                 let child_is_row = matches!(
                     self.get_node(*child),
-                    Ok(Node::Expression(Expression::Row { .. }))
+                    Ok(Node::Expression(Expression::Row(_)))
                 );
                 if child_is_row {
                     return true;
@@ -379,7 +390,7 @@ impl Plan {
     ) -> Result<Option<NodeId>, SbroadError> {
         let mut sq_set: HashSet<NodeId, RandomState> = HashSet::with_hasher(RandomState::new());
         for rel_id in rel_nodes {
-            if let Node::Relational(Relational::ScanSubQuery { .. }) = self.get_node(*rel_id)? {
+            if let Node::Relational(Relational::ScanSubQuery(_)) = self.get_node(*rel_id)? {
                 sq_set.insert(*rel_id);
             }
         }
@@ -468,11 +479,11 @@ impl Plan {
         let mut search_row = |row_id: NodeId| -> Result<bool, SbroadError> {
             let refs = self.get_row_list(row_id)?;
             for (pos_in_row, ref_id) in refs.iter().enumerate() {
-                let node @ Expression::Reference {
+                let ref node @ Expression::Reference(Reference {
                     targets,
                     position: ref_pos,
                     ..
-                } = self.get_expression_node(*ref_id)?
+                }) = self.get_expression_node(*ref_id)?
                 else {
                     continue;
                 };
@@ -581,7 +592,7 @@ impl Plan {
         if !strategy
             .children_policy
             .iter()
-            .all(|(node, _)| children_set.get(node).is_some())
+            .all(|(node, _)| children_set.contains(node))
         {
             return Err(SbroadError::FailedTo(
                 Action::Add,
@@ -700,7 +711,7 @@ impl Plan {
         op_id: NodeId,
     ) -> Result<Option<(NodeId, MotionPolicy)>, SbroadError> {
         let unary_op_expr = self.get_expression_node(op_id)?;
-        let Expression::Unary { child, op } = unary_op_expr else {
+        let Expression::Unary(UnaryExpr { child, op }) = unary_op_expr else {
             return Err(SbroadError::Invalid(
                 Entity::Expression,
                 Some(format_smolstr!(
@@ -735,7 +746,7 @@ impl Plan {
         for level_node in &not_nodes {
             let not_node_id = level_node.1;
             let not_node = self.get_expression_node(not_node_id)?;
-            if let Expression::Unary { child, .. } = not_node {
+            if let Expression::Unary(UnaryExpr { child, .. }) = not_node {
                 not_nodes_children.insert(*child);
             } else {
                 return Err(SbroadError::Invalid(
@@ -789,15 +800,14 @@ impl Plan {
     /// - If the node is not a join node.
     /// - Join node has no children.
     fn get_join_children(&self, join_id: NodeId) -> Result<Children<'_>, SbroadError> {
-        let join = self.get_relation_node(join_id)?;
-        if let Relational::Join { .. } = join {
+        if let Ok(children) = self.get_relational_children(join_id) {
+            Ok(children)
         } else {
-            return Err(SbroadError::Invalid(
+            Err(SbroadError::Invalid(
                 Entity::Relational,
                 Some("Join node is not an inner join.".into()),
-            ));
+            ))
         }
-        Ok(join.children())
     }
 
     /// Detect join child from the position map corresponding to the distribution key.
@@ -815,7 +825,9 @@ impl Plan {
                     format_smolstr!("{pos} in row map {row_map:?}"),
                 )
             })?;
-            if let Expression::Reference { targets, .. } = self.get_expression_node(column_id)? {
+            if let Expression::Reference(Reference { targets, .. }) =
+                self.get_expression_node(column_id)?
+            {
                 if let Some(targets) = targets {
                     for target in targets {
                         let child_id = *join_children.get(*target).ok_or_else(|| {
@@ -851,7 +863,7 @@ impl Plan {
     /// # Errors
     /// - If the node is not a row node.
     fn build_row_map(&self, row_id: NodeId) -> Result<HashMap<usize, NodeId>, SbroadError> {
-        let columns = self.get_expression_node(row_id)?.get_row_list()?;
+        let columns = self.get_row_list(row_id)?;
         let mut map: HashMap<usize, NodeId> = HashMap::new();
         for (pos, col) in columns.iter().enumerate() {
             map.insert(pos, *col);
@@ -913,10 +925,10 @@ impl Plan {
         let mut inner_positions: AHashSet<usize> = AHashSet::with_capacity(row_map.len());
         for (pos, col) in row_map {
             let expression = self.get_expression_node(*col)?;
-            if let Expression::Reference {
+            if let Expression::Reference(Reference {
                 targets: Some(targets),
                 ..
-            } = expression
+            }) = expression
             {
                 // Inner child of the join node is always the second one.
                 if targets == &[1; 1] {
@@ -942,9 +954,9 @@ impl Plan {
                     format_smolstr!("{pos} in row map {condition_row_map:?}"),
                 )
             })?;
-            if let Expression::Reference {
+            if let Expression::Reference(Reference {
                 targets, position, ..
-            } = self.get_expression_node(column_id)?
+            }) = self.get_expression_node(column_id)?
             {
                 if let Some(targets) = targets {
                     // Inner child of the join node is always the second one.
@@ -1166,7 +1178,7 @@ impl Plan {
             matches!(
                 self.get_node(node_id),
                 Ok(Node::Expression(
-                    Expression::Bool { .. } | Expression::Unary { op: Unary::Not, .. }
+                    Expression::Bool(_) | Expression::Unary(UnaryExpr { op: Unary::Not, .. })
                 ))
             )
         };
@@ -1183,13 +1195,13 @@ impl Plan {
             let expr = self.get_expression_node(node_id)?;
 
             // Under `not ... in ...` we should change the policy to `Full`
-            if let Expression::Unary {
+            if let Expression::Unary(UnaryExpr {
                 op: Unary::Not,
                 child,
-            } = expr
+            }) = expr
             {
                 let child_expr = self.get_expression_node(*child)?;
-                if let Expression::Bool { op: Bool::In, .. } = child_expr {
+                if let Expression::Bool(BoolExpr { op: Bool::In, .. }) = child_expr {
                     new_inner_policy = MotionPolicy::Full;
                     inner_map.insert(node_id, new_inner_policy.clone());
                     continue;
@@ -1224,12 +1236,12 @@ impl Plan {
             let left_expr = self.get_expression_node(bool_op.left)?;
             let right_expr = self.get_expression_node(bool_op.right)?;
             new_inner_policy = match (left_expr, right_expr) {
-                (Expression::Arithmetic { .. }, _) | (_, Expression::Arithmetic { .. }) => {
+                (Expression::Arithmetic(_), _) | (_, Expression::Arithmetic(_)) => {
                     MotionPolicy::Full
                 }
                 (
-                    Expression::Bool { .. } | Expression::Unary { .. },
-                    Expression::Bool { .. } | Expression::Unary { .. },
+                    Expression::Bool(_) | Expression::Unary(_),
+                    Expression::Bool(_) | Expression::Unary(_),
                 ) => {
                     let left_policy = inner_map
                         .get(&bool_op.left)
@@ -1250,7 +1262,7 @@ impl Plan {
                         }
                     }
                 }
-                (Expression::Row { .. }, Expression::Row { .. }) => {
+                (Expression::Row(_), Expression::Row(_)) => {
                     match bool_op.op {
                         Bool::Between => {
                             unreachable!("Between in redistribution")
@@ -1270,17 +1282,11 @@ impl Plan {
                         }
                     }
                 }
-                (
-                    Expression::Constant { .. },
-                    Expression::Bool { .. } | Expression::Unary { .. },
-                ) => inner_map
+                (Expression::Constant(_), Expression::Bool(_) | Expression::Unary(_)) => inner_map
                     .get(&bool_op.right)
                     .cloned()
                     .unwrap_or(MotionPolicy::Full),
-                (
-                    Expression::Bool { .. } | Expression::Unary { .. },
-                    Expression::Constant { .. },
-                ) => inner_map
+                (Expression::Bool(_) | Expression::Unary(_), Expression::Constant(_)) => inner_map
                     .get(&bool_op.left)
                     .cloned()
                     .unwrap_or(MotionPolicy::Full),
@@ -1368,9 +1374,9 @@ impl Plan {
                 // Otherwise, if they read some sharded table (Segment or Any),
                 // then they always have Motion, which was set before this function
                 // was called in `get_sq_node_strategy_for_unary_op`.
-                if let Expression::Bool {
+                if let Expression::Bool(BoolExpr {
                     op, left, right, ..
-                } = self.get_expression_node(node_id)?
+                }) = self.get_expression_node(node_id)?
                 {
                     // If some other operator is used, then the corresponding subquery
                     // already must have a Motion (in case it is reading non-global table),
@@ -1593,14 +1599,13 @@ impl Plan {
             return self.resolve_dml_node_conflict_for_global_table(update_id);
         }
 
-        if let Relational::Update { strategy: kind, .. } = self.get_relation_node(update_id)? {
+        if let Relational::Update(Update { strategy: kind, .. }) =
+            self.get_relation_node(update_id)?
+        {
             let mut map = Strategy::new(update_id);
             let table = self.dml_node_table(update_id)?;
             let child_id = self.get_relational_child(update_id, 0)?;
-            if !matches!(
-                self.get_relation_node(child_id)?,
-                Relational::Projection { .. }
-            ) {
+            if !matches!(self.get_relation_node(child_id)?, Relational::Projection(_)) {
                 return Err(SbroadError::Invalid(
                     Entity::Update,
                     Some(format_smolstr!(
@@ -1778,7 +1783,7 @@ impl Plan {
         let mut map = Strategy::new(rel_id);
         let child_id = self.dml_child_id(rel_id)?;
         let child_node = self.get_relation_node(child_id)?;
-        if !matches!(child_node, Relational::Motion { .. }) {
+        if !matches!(child_node, Relational::Motion(_)) {
             map.add_child(child_id, MotionPolicy::Full, Program::default());
         }
         Ok(map)
@@ -1886,7 +1891,7 @@ impl Plan {
 
     #[allow(clippy::too_many_lines)]
     fn resolve_except_conflicts(&mut self, rel_id: NodeId) -> Result<Strategy, SbroadError> {
-        if !matches!(self.get_relation_node(rel_id)?, Relational::Except { .. }) {
+        if !matches!(self.get_relation_node(rel_id)?, Relational::Except(_)) {
             return Err(SbroadError::Invalid(
                 Entity::Relational,
                 Some("expected Except node".into()),
@@ -2027,12 +2032,12 @@ impl Plan {
         let cloned_left_id = SubtreeCloner::clone_subtree(self, left_id, left_id.offset as usize)?;
         let right_output_id = self.get_relational_output(right_id)?;
         let intersect_output_id = self.clone_expr_subtree(right_output_id)?;
-        let intersect = Relational::Intersect {
+        let intersect = Intersect {
             left: right_id,
             right: cloned_left_id,
             output: intersect_output_id,
         };
-        let intersect_id = self.add_relational(intersect)?;
+        let intersect_id = self.add_relational(intersect.into())?;
 
         self.change_child(except_id, right_id, intersect_id)?;
 
@@ -2046,7 +2051,7 @@ impl Plan {
     fn resolve_union_conflicts(&mut self, rel_id: NodeId) -> Result<Strategy, SbroadError> {
         if !matches!(
             self.get_relation_node(rel_id)?,
-            Relational::UnionAll { .. } | Relational::Union { .. }
+            Relational::UnionAll(_) | Relational::Union(_)
         ) {
             return Err(SbroadError::Invalid(
                 Entity::Relational,
@@ -2060,8 +2065,8 @@ impl Plan {
         let right_output_id = self.get_relation_node(right_id)?.output();
 
         {
-            let left_output_row = self.get_expression_node(left_output_id)?.get_row_list()?;
-            let right_output_row = self.get_expression_node(right_output_id)?.get_row_list()?;
+            let left_output_row = self.get_row_list(left_output_id)?;
+            let right_output_row = self.get_row_list(right_output_id)?;
             if left_output_row.len() != right_output_row.len() {
                 return Err(SbroadError::UnexpectedNumberOfValues(format_smolstr!(
                     "Except node children have different row lengths: left {}, right {}",
@@ -2152,7 +2157,7 @@ impl Plan {
                 continue;
             }
 
-            let node = self.get_relation_node(id)?.clone();
+            let node = self.get_relation_node(id)?.get_rel_owned();
 
             // Some transformations (Union) need to add new nodes above
             // themselves, because we don't store parent references,
@@ -2172,15 +2177,15 @@ impl Plan {
                 // At the moment our grammar and IR constructors
                 // don't allow projection and values row with
                 // sub queries.
-                Relational::ScanRelation { output, .. }
-                | Relational::ScanSubQuery { output, .. }
-                | Relational::GroupBy { output, .. }
-                | Relational::Intersect { output, .. }
-                | Relational::Having { output, .. }
-                | Relational::ValuesRow { output, .. } => {
+                RelOwned::ScanRelation(ScanRelation { output, .. })
+                | RelOwned::ScanSubQuery(ScanSubQuery { output, .. })
+                | RelOwned::GroupBy(GroupBy { output, .. })
+                | RelOwned::Intersect(Intersect { output, .. })
+                | RelOwned::Having(Having { output, .. })
+                | RelOwned::ValuesRow(ValuesRow { output, .. }) => {
                     self.set_distribution(output)?;
                 }
-                Relational::Limit { output, limit, .. } => {
+                RelOwned::Limit(Limit { output, limit, .. }) => {
                     let rel_child_id = self.get_relational_child(id, 0)?;
                     let child_dist =
                         self.get_distribution(self.get_relational_output(rel_child_id)?)?;
@@ -2209,7 +2214,7 @@ impl Plan {
                         }
                     }
                 }
-                Relational::OrderBy { output, .. } => {
+                RelOwned::OrderBy(OrderBy { output, .. }) => {
                     let rel_child_id = self.get_relational_child(id, 0)?;
 
                     let child_dist =
@@ -2225,13 +2230,13 @@ impl Plan {
                     }
                     self.set_dist(output, Distribution::Single)?;
                 }
-                Relational::Values { output, .. } => {
+                RelOwned::Values(Values { output, .. }) => {
                     self.set_dist(output, Distribution::Global)?;
                 }
-                Relational::Projection {
+                RelOwned::Projection(Projection {
                     output: proj_output_id,
                     ..
-                } => {
+                }) => {
                     let child_dist = self.get_distribution(
                         self.get_relational_output(self.get_relational_child(id, 0)?)?,
                     )?;
@@ -2248,14 +2253,14 @@ impl Plan {
                         self.set_projection_distribution(id)?;
                     }
                 }
-                Relational::Motion { .. } => {
+                RelOwned::Motion { .. } => {
                     // We can apply this transformation only once,
                     // i.e. to the plan without any motion nodes.
                     return Err(SbroadError::DuplicatedValue(SmolStr::from(
                         "IR already has Motion nodes.",
                     )));
                 }
-                Relational::Selection { output, filter, .. } => {
+                RelOwned::Selection(Selection { output, filter, .. }) => {
                     let strategy = self.resolve_sub_query_conflicts(id, filter)?;
                     self.create_motion_nodes(strategy)?;
                     if let Some(dist) = self.dist_from_subqueries(id)? {
@@ -2264,12 +2269,12 @@ impl Plan {
                         self.set_distribution(output)?;
                     }
                 }
-                Relational::Join {
+                RelOwned::Join(Join {
                     output,
                     condition,
                     kind,
                     ..
-                } => {
+                }) => {
                     self.resolve_join_conflicts(id, condition, &kind)?;
                     if let Some(dist) = self.dist_from_subqueries(id)? {
                         self.set_dist(output, dist)?;
@@ -2277,26 +2282,26 @@ impl Plan {
                         self.set_distribution(output)?;
                     }
                 }
-                Relational::Delete { .. } => {
+                RelOwned::Delete { .. } => {
                     let strategy = self.resolve_delete_conflicts(id)?;
                     self.create_motion_nodes(strategy)?;
                 }
-                Relational::Insert { .. } => {
+                RelOwned::Insert { .. } => {
                     // Insert output tuple already has relation's distribution.
                     let strategy = self.resolve_insert_conflicts(id)?;
                     self.create_motion_nodes(strategy)?;
                 }
-                Relational::Update { output, .. } => {
+                RelOwned::Update(Update { output, .. }) => {
                     let strategy = self.resolve_update_conflicts(id)?;
                     self.create_motion_nodes(strategy)?;
                     self.set_distribution(output)?;
                 }
-                Relational::Except { output, .. } => {
+                RelOwned::Except(Except { output, .. }) => {
                     let strategy = self.resolve_except_conflicts(id)?;
                     self.create_motion_nodes(strategy)?;
                     self.set_distribution(output)?;
                 }
-                Relational::Union { output, .. } => {
+                RelOwned::Union(Union { output, .. }) => {
                     let strategy = self.resolve_union_conflicts(id)?;
                     self.create_motion_nodes(strategy)?;
                     self.set_distribution(output)?;
@@ -2307,12 +2312,12 @@ impl Plan {
                     )?;
                     old_new.insert(id, new_top_id);
                 }
-                Relational::UnionAll { output, .. } => {
+                RelOwned::UnionAll(UnionAll { output, .. }) => {
                     let strategy = self.resolve_union_conflicts(id)?;
                     self.create_motion_nodes(strategy)?;
                     self.set_distribution(output)?;
                 }
-                Relational::ScanCte { output, child, .. } => {
+                RelOwned::ScanCte(ScanCte { output, child, .. }) => {
                     // Possible, current CTE subtree has already been resolved and we
                     // can just copy the corresponding motion node.
                     if let Some(motion_id) = cte_motions.get(&child) {
@@ -2339,6 +2344,7 @@ impl Plan {
                     }
                 }
             }
+
             visited.insert(id);
         }
 
@@ -2353,7 +2359,6 @@ impl Plan {
         let top_id = self.get_top()?;
         let slices = self.calculate_slices(top_id)?;
         self.set_slices(slices);
-
         Ok(())
     }
 
@@ -2371,7 +2376,7 @@ impl Plan {
         let mut map: HashMap<usize, usize> = HashMap::new();
         let mut max_level: usize = 0;
         for LevelNode(level, id) in bft_tree.iter(top_id) {
-            if let Node::Relational(Relational::Motion { .. }) = self.get_node(id)? {
+            if let Node::Relational(Relational::Motion(_)) = self.get_node(id)? {
                 let key: usize = match map.entry(level) {
                     Entry::Occupied(o) => *o.into_mut(),
                     Entry::Vacant(v) => {
diff --git a/sbroad-core/src/ir/transformation/redistribution/dml.rs b/sbroad-core/src/ir/transformation/redistribution/dml.rs
index b29de812b..96a901a73 100644
--- a/sbroad-core/src/ir/transformation/redistribution/dml.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/dml.rs
@@ -1,6 +1,7 @@
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::NodeId;
-use crate::ir::operator::{ConflictStrategy, Relational, UpdateStrategy};
+use crate::ir::node::relational::{MutRelational, Relational};
+use crate::ir::node::{Delete, Insert, Motion, NodeId, Update};
+use crate::ir::operator::{ConflictStrategy, UpdateStrategy};
 use crate::ir::relation::{Column, Table};
 use crate::ir::transformation::redistribution::MotionOpcode;
 use crate::ir::Plan;
@@ -17,9 +18,9 @@ impl Plan {
     /// - `Insert` has 0 or more than 1 child
     pub fn dml_child_id(&self, dml_node_id: NodeId) -> Result<NodeId, SbroadError> {
         let dml_node = self.get_relation_node(dml_node_id)?;
-        if let Relational::Insert { children, .. }
-        | Relational::Update { children, .. }
-        | Relational::Delete { children, .. } = dml_node
+        if let Relational::Insert(Insert { children, .. })
+        | Relational::Update(Update { children, .. })
+        | Relational::Delete(Delete { children, .. }) = dml_node
         {
             if let (Some(child), None) = (children.first(), children.get(1)) {
                 return Ok(*child);
@@ -44,9 +45,9 @@ impl Plan {
         insert_id: NodeId,
     ) -> Result<&ConflictStrategy, SbroadError> {
         let insert = self.get_relation_node(insert_id)?;
-        if let Relational::Insert {
+        if let Relational::Insert(Insert {
             conflict_strategy, ..
-        } = insert
+        }) = insert
         {
             return Ok(conflict_strategy);
         }
@@ -72,7 +73,7 @@ impl Plan {
         // output columns.
         let child_id = self.dml_child_id(insert_id)?;
         let child_output_id = self.get_relation_node(child_id)?.output();
-        let child_row = self.get_expression_node(child_output_id)?.get_row_list()?;
+        let child_row = self.get_row_list(child_output_id)?;
         if columns.len() != child_row.len() {
             return Err(SbroadError::Invalid(
                 Entity::Node,
@@ -117,7 +118,7 @@ impl Plan {
     /// - node is not `Insert`
     pub(crate) fn insert_columns(&self, insert_id: NodeId) -> Result<&[usize], SbroadError> {
         let insert = self.get_relation_node(insert_id)?;
-        if let Relational::Insert { ref columns, .. } = insert {
+        if let Relational::Insert(Insert { ref columns, .. }) = insert {
             return Ok(columns);
         }
         Err(SbroadError::Invalid(
@@ -132,9 +133,9 @@ impl Plan {
     /// - Node is not an `Insert`
     pub fn dml_node_table(&self, node_id: NodeId) -> Result<&Table, SbroadError> {
         let node = self.get_relation_node(node_id)?;
-        if let Relational::Insert { relation, .. }
-        | Relational::Update { relation, .. }
-        | Relational::Delete { relation, .. } = node
+        if let Relational::Insert(Insert { relation, .. })
+        | Relational::Update(Update { relation, .. })
+        | Relational::Delete(Delete { relation, .. }) = node
         {
             return self.get_relation_or_error(relation);
         }
@@ -155,13 +156,13 @@ impl Plan {
         len: usize,
     ) -> Result<(), SbroadError> {
         let node = self.get_mut_relation_node(update_id)?;
-        if let Relational::Update {
+        if let MutRelational::Update(Update {
             strategy:
                 UpdateStrategy::ShardedUpdate {
                     delete_tuple_len, ..
                 },
             ..
-        } = node
+        }) = node
         {
             *delete_tuple_len = Some(len);
         } else {
@@ -181,14 +182,14 @@ impl Plan {
     /// - length not set on current `Update` node
     pub fn get_update_delete_tuple_len(&self, update_id: NodeId) -> Result<usize, SbroadError> {
         let node = self.get_relation_node(update_id)?;
-        if let Relational::Update {
+        if let Relational::Update(Update {
             strategy:
                 UpdateStrategy::ShardedUpdate {
                     delete_tuple_len: Some(len),
                     ..
                 },
             ..
-        } = node
+        }) = node
         {
             return Ok(*len);
         }
@@ -211,7 +212,7 @@ impl Plan {
         opcode_idx: usize,
     ) -> Result<&MotionOpcode, SbroadError> {
         let node = self.get_relation_node(motion_id)?;
-        if let Relational::Motion { program, .. } = node {
+        if let Relational::Motion(Motion { program, .. }) = node {
             if let Some(op) = program.0.get(opcode_idx) {
                 return Ok(op);
             }
@@ -232,7 +233,7 @@ impl Plan {
     /// - Node is not an `Update`
     pub fn is_sharded_update(&self, update_id: NodeId) -> Result<bool, SbroadError> {
         let node = self.get_relation_node(update_id)?;
-        if let Relational::Update { strategy, .. } = node {
+        if let Relational::Update(Update { strategy, .. }) = node {
             return Ok(matches!(strategy, UpdateStrategy::ShardedUpdate { .. }));
         }
         Err(SbroadError::Invalid(
diff --git a/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs b/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs
index 67e531528..82541cf4f 100644
--- a/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/eq_cols.rs
@@ -1,8 +1,13 @@
 use crate::errors::SbroadError;
-use crate::ir::expression::{Expression, ExpressionId, NodeId};
+use crate::ir::expression::ExpressionId;
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, Concat, ExprInParentheses, NodeId, Reference, Row,
+    StableFunction, Trim, UnaryExpr,
+};
 use crate::ir::operator::Bool;
 use crate::ir::transformation::redistribution::BoolOp;
-use crate::ir::tree::traversal::{PostOrder, PostOrderWithFilter, EXPR_CAPACITY};
+use crate::ir::tree::traversal::{LevelNode, PostOrder, PostOrderWithFilter, EXPR_CAPACITY};
 use crate::ir::{Node, Plan};
 use std::collections::{HashMap, HashSet};
 use std::slice::Iter;
@@ -71,20 +76,19 @@ impl ReferredMap {
         let mut referred = ReferredMap::with_capacity(EXPR_CAPACITY);
         let mut expr_tree =
             PostOrder::with_capacity(|node| plan.nodes.expr_iter(node, false), EXPR_CAPACITY);
-        for level_node in expr_tree.iter(condition_id) {
-            let node_id = level_node.1;
+        for LevelNode(_, node_id) in expr_tree.iter(condition_id) {
             let expr = plan.get_expression_node(node_id)?;
             let res = match expr {
-                Expression::Bool { left, right, .. }
-                | Expression::Arithmetic { left, right, .. }
-                | Expression::Concat { left, right, .. } => referred
+                Expression::Bool(BoolExpr { left, right, .. })
+                | Expression::Arithmetic(ArithmeticExpr { left, right, .. })
+                | Expression::Concat(Concat { left, right, .. }) => referred
                     .get_or_none(*left)
                     .add(referred.get_or_none(*right)),
-                Expression::Case {
+                Expression::Case(Case {
                     search_expr,
                     when_blocks,
                     else_expr,
-                } => {
+                }) => {
                     let mut res = Referred::None;
                     if let Some(search_expr) = search_expr {
                         res = res.add(referred.get_or_none(*search_expr));
@@ -98,18 +102,18 @@ impl ReferredMap {
                     }
                     res
                 }
-                Expression::Trim {
+                Expression::Trim(Trim {
                     pattern, target, ..
-                } => match pattern {
+                }) => match pattern {
                     Some(pattern) => referred
                         .get_or_none(*pattern)
                         .add(referred.get_or_none(*target)),
                     None => referred.get_or_none(*target).clone(),
                 },
-                Expression::Constant { .. } | Expression::CountAsterisk => Referred::None,
-                Expression::Reference {
+                Expression::Constant { .. } | Expression::CountAsterisk { .. } => Referred::None,
+                Expression::Reference(Reference {
                     targets, parent, ..
-                } => {
+                }) => {
                     if *parent == Some(join_id) && *targets == Some(vec![1]) {
                         Referred::Inner
                     } else if *parent == Some(join_id) && *targets == Some(vec![0]) {
@@ -118,16 +122,16 @@ impl ReferredMap {
                         Referred::None
                     }
                 }
-                Expression::Row { list: children, .. }
-                | Expression::StableFunction { children, .. } => {
+                Expression::Row(Row { list: children, .. })
+                | Expression::StableFunction(StableFunction { children, .. }) => {
                     children.iter().fold(Referred::None, |acc, x| {
                         acc.add(referred.get(*x).unwrap_or(&Referred::None))
                     })
                 }
-                Expression::Alias { child, .. }
-                | Expression::ExprInParentheses { child }
-                | Expression::Cast { child, .. }
-                | Expression::Unary { child, .. } => {
+                Expression::Alias(Alias { child, .. })
+                | Expression::ExprInParentheses(ExprInParentheses { child })
+                | Expression::Cast(Cast { child, .. })
+                | Expression::Unary(UnaryExpr { child, .. }) => {
                     referred.get(*child).unwrap_or(&Referred::None).clone()
                 }
             };
@@ -291,18 +295,18 @@ impl EqualityCols {
             let right_node = plan.get_expression_node(*right_id)?;
             match (left_node, right_node) {
                 (
-                    Expression::Reference {
+                    Expression::Reference(Reference {
                         targets: targets_left,
                         position: pos_left,
                         parent: parent_left,
                         col_type: col_type_left,
-                    },
-                    Expression::Reference {
+                    }),
+                    Expression::Reference(Reference {
                         targets: targets_right,
                         position: pos_right,
                         parent: parent_right,
                         col_type: col_type_right,
-                    },
+                    }),
                 ) => {
                     // TODO: compare types only if the runtime requires it.
 
@@ -322,7 +326,7 @@ impl EqualityCols {
                         new_eq_cols.add_equality_pair(inner_pos, outer_pos);
                     };
                 }
-                (Expression::Constant { .. }, _) | (_, Expression::Constant { .. }) => {}
+                (Expression::Constant(_), _) | (_, Expression::Constant(_)) => {}
                 (_, _) => {
                     // if some kind of transformation is applied to another side of equality
                     // operator, we can't do repartition join, for example:
@@ -360,12 +364,12 @@ impl EqualityCols {
         let right_expr = plan.get_expression_node(op.right)?;
         let res = match (left_expr, right_expr) {
             (
-                Expression::Row {
+                Expression::Row(Row {
                     list: list_left, ..
-                },
-                Expression::Row {
+                }),
+                Expression::Row(Row {
                     list: list_right, ..
-                },
+                }),
             ) => match op.op {
                 Bool::Eq | Bool::In => EqualityCols::eq_cols_for_eq(
                     list_left, list_right, node_id, inner_id, plan, refers_to,
@@ -489,7 +493,7 @@ impl EqualityCols {
         let mut node_eq_cols: EqualityColsMap = EqualityColsMap::new();
         let refers_to = ReferredMap::new_from_join_condition(plan, condition_id, join_id)?;
         let filter = |node_id: NodeId| -> bool {
-            if let Ok(Node::Expression(Expression::Bool { .. })) = plan.get_node(node_id) {
+            if let Ok(Node::Expression(Expression::Bool(_))) = plan.get_node(node_id) {
                 return true;
             }
             false
@@ -505,7 +509,7 @@ impl EqualityCols {
             let left_expr = plan.get_expression_node(bool_op.left)?;
             let right_expr = plan.get_expression_node(bool_op.right)?;
             let new_eq_cols = match (left_expr, right_expr) {
-                (Expression::Row { .. }, Expression::Row { .. }) => {
+                (Expression::Row(_), Expression::Row(_)) => {
                     EqualityCols::eq_cols_for_rows(&bool_op, node_id, &refers_to, inner_id, plan)?
                 }
                 (_, _) => {
diff --git a/sbroad-core/src/ir/transformation/redistribution/groupby.rs b/sbroad-core/src/ir/transformation/redistribution/groupby.rs
index ac587a759..132b9857c 100644
--- a/sbroad-core/src/ir/transformation/redistribution/groupby.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/groupby.rs
@@ -4,18 +4,21 @@ use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::to_user;
 use crate::ir::aggregates::{generate_local_alias_for_aggr, AggregateKind, SimpleAggregate};
 use crate::ir::distribution::Distribution;
-use crate::ir::expression::Expression::StableFunction;
 use crate::ir::expression::{
-    ColumnPositionMap, Comparator, Expression, FunctionFeature, NodeId, ReferencePolicy,
-    EXPR_HASH_DEPTH,
+    ColumnPositionMap, Comparator, FunctionFeature, ReferencePolicy, EXPR_HASH_DEPTH,
+};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::relational::{MutRelational, Relational};
+use crate::ir::node::{
+    Alias, ArenaType, ArithmeticExpr, BoolExpr, Case, Cast, Concat, Constant, ExprInParentheses,
+    GroupBy, Having, NodeId, Projection, Reference, Row, StableFunction, Trim, UnaryExpr,
 };
-use crate::ir::operator::Relational;
 use crate::ir::relation::Type;
 use crate::ir::transformation::redistribution::{
     MotionKey, MotionPolicy, Program, Strategy, Target,
 };
 use crate::ir::tree::traversal::{BreadthFirst, PostOrderWithFilter, EXPR_CAPACITY};
-use crate::ir::{ArenaType, Node, Plan};
+use crate::ir::{Node, Plan};
 use std::collections::{HashMap, HashSet};
 
 use crate::ir::function::{Behavior, Function};
@@ -131,6 +134,7 @@ impl<'plan, 'args> PartialEq<Self> for AggregateSignature<'plan, 'args> {
 
 impl<'plan, 'args> Eq for AggregateSignature<'plan, 'args> {}
 
+#[derive(Debug)]
 struct GroupingExpression<'plan> {
     pub id: NodeId,
     pub plan: &'plan Plan,
@@ -194,7 +198,7 @@ impl<'plan> AggrCollector<'plan> {
 
     fn find(&mut self, current: NodeId, parent: Option<NodeId>) -> Result<(), SbroadError> {
         let expr = self.plan.get_expression_node(current)?;
-        if let StableFunction { name, feature, .. } = expr {
+        if let Expression::StableFunction(StableFunction { name, feature, .. }) = expr {
             let is_distinct = matches!(feature, Some(FunctionFeature::Distinct));
             if let Some(aggr) = SimpleAggregate::new(name, current) {
                 let Some(parent_rel) = self.parent_rel else {
@@ -290,7 +294,7 @@ impl<'plan> ExpressionMapper<'plan> {
         };
         let is_ref = matches!(
             self.plan.get_expression_node(current),
-            Ok(Expression::Reference { .. })
+            Ok(Expression::Reference(_))
         );
         let is_sq_ref = is_ref
             && self.plan.is_additional_child_of_rel(
@@ -327,7 +331,7 @@ impl<'plan> ExpressionMapper<'plan> {
             let column_name = {
                 let node = self.plan.get_expression_node(current)?;
                 self.plan
-                    .get_alias_from_reference_node(node)
+                    .get_alias_from_reference_node(&node)
                     .unwrap_or("'failed to get column name'")
             };
             return Err(SbroadError::Invalid(
@@ -393,25 +397,27 @@ impl Plan {
             if let Node::Expression(right) = r {
                 match left {
                     Expression::Alias { .. } => {}
-                    Expression::ExprInParentheses { child: l_child } => {
+                    Expression::ExprInParentheses(ExprInParentheses { child: l_child }) => {
                         // TODO: Should we compare expressions ignoring parentheses?
-                        if let Expression::ExprInParentheses { child: r_child } = right {
+                        if let Expression::ExprInParentheses(ExprInParentheses { child: r_child }) =
+                            right
+                        {
                             return self.are_aggregate_subtrees_equal(*l_child, *r_child);
                         }
                     }
-                    Expression::CountAsterisk => {
-                        return Ok(matches!(right, Expression::CountAsterisk))
+                    Expression::CountAsterisk(_) => {
+                        return Ok(matches!(right, Expression::CountAsterisk(_)))
                     }
-                    Expression::Bool {
+                    Expression::Bool(BoolExpr {
                         left: left_left,
                         op: op_left,
                         right: right_left,
-                    } => {
-                        if let Expression::Bool {
+                    }) => {
+                        if let Expression::Bool(BoolExpr {
                             left: left_right,
                             op: op_right,
                             right: right_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*op_left == *op_right
                                 && self.are_aggregate_subtrees_equal(*left_left, *left_right)?
@@ -419,16 +425,16 @@ impl Plan {
                                     .are_aggregate_subtrees_equal(*right_left, *right_right)?);
                         }
                     }
-                    Expression::Case {
+                    Expression::Case(Case {
                         search_expr: search_expr_left,
                         when_blocks: when_blocks_left,
                         else_expr: else_expr_left,
-                    } => {
-                        if let Expression::Case {
+                    }) => {
+                        if let Expression::Case(Case {
                             search_expr: search_expr_right,
                             when_blocks: when_blocks_right,
                             else_expr: else_expr_right,
-                        } = right
+                        }) = right
                         {
                             let mut search_expr_equal = false;
                             if let (Some(search_expr_left), Some(search_expr_right)) =
@@ -463,46 +469,46 @@ impl Plan {
                             return Ok(search_expr_equal && when_blocks_equal && else_expr_equal);
                         }
                     }
-                    Expression::Arithmetic {
+                    Expression::Arithmetic(ArithmeticExpr {
                         op: op_left,
                         left: l_left,
                         right: r_left,
-                    } => {
-                        if let Expression::Arithmetic {
+                    }) => {
+                        if let Expression::Arithmetic(ArithmeticExpr {
                             op: op_right,
                             left: l_right,
                             right: r_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*op_left == *op_right
                                 && self.are_aggregate_subtrees_equal(*l_left, *l_right)?
                                 && self.are_aggregate_subtrees_equal(*r_left, *r_right)?);
                         }
                     }
-                    Expression::Cast {
+                    Expression::Cast(Cast {
                         child: child_left,
                         to: to_left,
-                    } => {
-                        if let Expression::Cast {
+                    }) => {
+                        if let Expression::Cast(Cast {
                             child: child_right,
                             to: to_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*to_left == *to_right
                                 && self
                                     .are_aggregate_subtrees_equal(*child_left, *child_right)?);
                         }
                     }
-                    Expression::Trim {
+                    Expression::Trim(Trim {
                         kind: kind_left,
                         pattern: pattern_left,
                         target: target_left,
-                    } => {
-                        if let Expression::Trim {
+                    }) => {
+                        if let Expression::Trim(Trim {
                             kind: kind_right,
                             pattern: pattern_right,
                             target: target_right,
-                        } = right
+                        }) = right
                         {
                             match (pattern_left, pattern_right) {
                                 (Some(p_left), Some(p_right)) => {
@@ -525,14 +531,14 @@ impl Plan {
                             }
                         }
                     }
-                    Expression::Concat {
+                    Expression::Concat(Concat {
                         left: left_left,
                         right: right_left,
-                    } => {
-                        if let Expression::Concat {
+                    }) => {
+                        if let Expression::Concat(Concat {
                             left: left_right,
                             right: right_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(self
                                 .are_aggregate_subtrees_equal(*left_left, *left_right)?
@@ -540,44 +546,44 @@ impl Plan {
                                     .are_aggregate_subtrees_equal(*right_left, *right_right)?);
                         }
                     }
-                    Expression::Constant { value: value_left } => {
-                        if let Expression::Constant { value: value_right } = right {
+                    Expression::Constant(Constant { value: value_left }) => {
+                        if let Expression::Constant(Constant { value: value_right }) = right {
                             return Ok(*value_left == *value_right);
                         }
                     }
                     Expression::Reference { .. } => {
                         if let Expression::Reference { .. } = right {
-                            let alias_left = self.get_alias_from_reference_node(left)?;
-                            let alias_right = self.get_alias_from_reference_node(right)?;
+                            let alias_left = self.get_alias_from_reference_node(&left)?;
+                            let alias_right = self.get_alias_from_reference_node(&right)?;
                             return Ok(alias_left == alias_right);
                         }
                     }
-                    Expression::Row {
+                    Expression::Row(Row {
                         list: list_left, ..
-                    } => {
-                        if let Expression::Row {
+                    }) => {
+                        if let Expression::Row(Row {
                             list: list_right, ..
-                        } = right
+                        }) = right
                         {
                             return Ok(list_left.iter().zip(list_right.iter()).all(|(l, r)| {
                                 self.are_aggregate_subtrees_equal(*l, *r).unwrap_or(false)
                             }));
                         }
                     }
-                    Expression::StableFunction {
+                    Expression::StableFunction(StableFunction {
                         name: name_left,
                         children: children_left,
                         feature: feature_left,
                         func_type: func_type_left,
                         is_system: is_aggr_left,
-                    } => {
-                        if let Expression::StableFunction {
+                    }) => {
+                        if let Expression::StableFunction(StableFunction {
                             name: name_right,
                             children: children_right,
                             feature: feature_right,
                             func_type: func_type_right,
                             is_system: is_aggr_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(name_left == name_right
                                 && feature_left == feature_right
@@ -590,14 +596,14 @@ impl Plan {
                                 ));
                         }
                     }
-                    Expression::Unary {
+                    Expression::Unary(UnaryExpr {
                         op: op_left,
                         child: child_left,
-                    } => {
-                        if let Expression::Unary {
+                    }) => {
+                        if let Expression::Unary(UnaryExpr {
                             op: op_right,
                             child: child_right,
-                        } = right
+                        }) = right
                         {
                             return Ok(*op_left == *op_right
                                 && self
@@ -636,7 +642,7 @@ impl Plan {
                 matches!(
                     self.get_node(node_id),
                     Ok(Node::Expression(
-                        Expression::StableFunction { .. } | Expression::Reference { .. }
+                        Expression::StableFunction(_) | Expression::Reference(_)
                     ))
                 )
             };
@@ -650,10 +656,12 @@ impl Plan {
                 let node_id = level_node.1;
                 let node = self.get_node(node_id)?;
                 match node {
-                    Node::Expression(Expression::Reference { .. }) => {
+                    Node::Expression(Expression::Reference(_)) => {
                         contains_at_least_one_col = true;
                     }
-                    Node::Expression(Expression::StableFunction { name, .. }) => {
+                    Node::Expression(Expression::StableFunction(StableFunction {
+                        name, ..
+                    })) => {
                         if Expression::is_aggregate_name(name) {
                             return Err(SbroadError::Invalid(
                                 Entity::Query,
@@ -689,14 +697,14 @@ impl Plan {
         expr_parent: Option<NodeId>,
     ) -> Result<NodeId, SbroadError> {
         let final_output = self.add_row_for_output(child_id, &[], true)?;
-        let groupby = Relational::GroupBy {
+        let groupby = GroupBy {
             children: [child_id].to_vec(),
             gr_cols: grouping_exprs.to_vec(),
             output: final_output,
             is_final,
         };
 
-        let groupby_id = self.add_relational(groupby)?;
+        let groupby_id = self.add_relational(groupby.into())?;
 
         self.replace_parent_in_subtree(final_output, None, Some(groupby_id))?;
         for expr in grouping_exprs {
@@ -719,12 +727,12 @@ impl Plan {
         for node_id in finals {
             let node = self.get_relation_node(*node_id)?;
             match node {
-                Relational::Projection { output, .. } => {
+                Relational::Projection(Projection { output, .. }) => {
                     for col in self.get_row_list(*output)? {
                         collector.collect_aggregates(*col, *node_id)?;
                     }
                 }
-                Relational::Having { filter, .. } => {
+                Relational::Having(Having { filter, .. }) => {
                     collector.collect_aggregates(*filter, *node_id)?;
                 }
                 _ => {
@@ -795,7 +803,7 @@ impl Plan {
         let max_reduce_nodes = 2;
         for _ in 0..=max_reduce_nodes {
             match self.get_relation_node(next)? {
-                Relational::Projection { .. } | Relational::Having { .. } => {
+                Relational::Projection(_) | Relational::Having(_) => {
                     finals.push(next);
                     next = get_first_child(next)?;
                 }
@@ -849,22 +857,22 @@ impl Plan {
         let mut gr_expr_map: GroupbyExpressionsMap = HashMap::new();
         let mut upper = upper;
 
-        let mut has_groupby = matches!(self.get_relation_node(upper)?, Relational::GroupBy { .. });
+        let mut has_groupby = matches!(self.get_relation_node(upper)?, Relational::GroupBy(_));
 
         if !has_groupby && !has_aggregates {
             if let Some(proj_id) = finals.first() {
-                if let Relational::Projection {
+                if let Relational::Projection(Projection {
                     is_distinct,
                     output,
                     ..
-                } = self.get_relation_node(*proj_id)?
+                }) = self.get_relation_node(*proj_id)?
                 {
                     if *is_distinct {
                         let proj_cols_len = self.get_row_list(*output)?.len();
                         let mut grouping_exprs: Vec<NodeId> = Vec::with_capacity(proj_cols_len);
                         for i in 0..proj_cols_len {
                             let aliased_col = self.get_proj_col(*proj_id, i)?;
-                            let proj_col_id = if let Expression::Alias { child, .. } =
+                            let proj_col_id = if let Expression::Alias(Alias { child, .. }) =
                                 self.get_expression_node(aliased_col)?
                             {
                                 *child
@@ -901,12 +909,12 @@ impl Plan {
             let mut mapper = ExpressionMapper::new(&grouping_expr, self);
             for node_id in finals {
                 match self.get_relation_node(*node_id)? {
-                    Relational::Projection { output, .. } => {
+                    Relational::Projection(Projection { output, .. }) => {
                         for col in self.get_row_list(*output)? {
                             mapper.find_matches(*col, *node_id)?;
                         }
                     }
-                    Relational::Having { filter, .. } => {
+                    Relational::Having(Having { filter, .. }) => {
                         mapper.find_matches(*filter, *node_id)?;
                     }
                     _ => {}
@@ -920,12 +928,12 @@ impl Plan {
             for id in finals {
                 let node = self.get_relation_node(*id)?;
                 match node {
-                    Relational::Projection { output, .. } => {
+                    Relational::Projection(Projection { output, .. }) => {
                         for col in self.get_row_list(*output)? {
                             let filter = |node_id: NodeId| -> bool {
                                 matches!(
                                     self.get_node(node_id),
-                                    Ok(Node::Expression(Expression::Reference { .. }))
+                                    Ok(Node::Expression(Expression::Reference(_)))
                                 )
                             };
                             let mut dfs = PostOrderWithFilter::with_capacity(
@@ -938,8 +946,8 @@ impl Plan {
                             for level_node in nodes {
                                 let id = level_node.1;
                                 let n = self.get_expression_node(id)?;
-                                if let Expression::Reference { .. } = n {
-                                    let alias = match self.get_alias_from_reference_node(n) {
+                                if let Expression::Reference(_) = n {
+                                    let alias = match self.get_alias_from_reference_node(&n) {
                                         Ok(v) => v.to_smolstr(),
                                         Err(e) => e.to_smolstr(),
                                     };
@@ -949,7 +957,7 @@ impl Plan {
                             }
                         }
                     }
-                    Relational::Having { filter, .. } => {
+                    Relational::Having(Having { filter, .. }) => {
                         let mut bfs = BreadthFirst::with_capacity(
                             |x| self.nodes.aggregate_iter(x, false),
                             EXPR_CAPACITY,
@@ -959,7 +967,7 @@ impl Plan {
                         let nodes = bfs.take_nodes();
                         for level_node in nodes {
                             let id = level_node.1;
-                            if let Expression::Reference { .. } = self.get_expression_node(id)? {
+                            if let Expression::Reference(_) = self.get_expression_node(id)? {
                                 return Err(SbroadError::Invalid(
                                     Entity::Query,
                                     Some("HAVING argument must appear in the GROUP BY clause or be used in an aggregate function".into())
@@ -988,7 +996,7 @@ impl Plan {
     ) -> Result<NodeId, SbroadError> {
         let mut local_proj_child_id = upper;
         if !additional_grouping_exprs.is_empty() {
-            if let Relational::GroupBy { gr_cols, .. } =
+            if let MutRelational::GroupBy(GroupBy { gr_cols, .. }) =
                 self.get_mut_relation_node(local_proj_child_id)?
             {
                 gr_cols.extend(additional_grouping_exprs);
@@ -1068,12 +1076,13 @@ impl Plan {
         let (child_id, proj_output_cols, groupby_local_aliases, grouping_positions) =
             self.create_columns_for_local_proj(aggr_infos, child_id, grouping_exprs)?;
         let proj_output = self.nodes.add_row(proj_output_cols, None);
-        let proj = Relational::Projection {
+        let proj = Projection {
             output: proj_output,
             children: vec![child_id],
             is_distinct: false,
         };
-        let proj_id = self.add_relational(proj)?;
+        let proj_id = self.add_relational(proj.into())?;
+
         for info in aggr_infos {
             // We take expressions inside aggregate functions from Final projection,
             // so we need to update parent
@@ -1216,6 +1225,7 @@ impl Plan {
 
         let mut alias_to_pos: HashMap<Rc<String>, usize> = HashMap::new();
         // add grouping expressions to local projection
+
         for (pos, (gr_expr, local_alias)) in
             unique_grouping_exprs_for_local_stage.iter().enumerate()
         {
@@ -1282,7 +1292,7 @@ impl Plan {
                 continue;
             }
             let arguments = {
-                if let StableFunction { children, .. } =
+                if let Expression::StableFunction(StableFunction { children, .. }) =
                     self.get_expression_node(info.aggr.fun_id)?
                 {
                     children
@@ -1395,31 +1405,31 @@ impl Plan {
                     )),
                 ));
             }
-            let new_col = Expression::Reference {
+            let new_col = Reference {
                 position,
                 parent: None,
                 targets: Some(vec![0]),
                 col_type,
             };
-            nodes.push(Node::Expression(new_col));
+            nodes.push(new_col);
         }
         for node in nodes {
-            let new_col_id = self.nodes.push(node);
+            let new_col_id = self.nodes.push(node.into());
             gr_cols.push(new_col_id);
         }
         let output = self.add_row_for_output(child_id, &[], true)?;
-        let final_id = self.nodes.next_id(ArenaType::Default);
+        let final_id = self.nodes.next_id(ArenaType::Arena64);
         for col in &gr_cols {
             self.replace_parent_in_subtree(*col, None, Some(final_id))?;
         }
-        let final_groupby = Relational::GroupBy {
+        let final_groupby = GroupBy {
             gr_cols,
             children: vec![child_id],
             is_final: true,
             output,
         };
         self.replace_parent_in_subtree(output, None, Some(final_id))?;
-        self.add_relational(final_groupby)?;
+        self.add_relational(final_groupby.into())?;
 
         Ok(final_id)
     }
@@ -1488,21 +1498,21 @@ impl Plan {
                         )),
                     ));
                 };
-                let new_ref = Expression::Reference {
+                let new_ref = Reference {
                     parent: Some(rel_id),
                     targets: Some(vec![0]),
                     position,
                     col_type,
                 };
-                nodes.push((parent, expr_id, gr_expr_id, Node::Expression(new_ref)));
+                nodes.push((parent, expr_id, gr_expr_id, new_ref));
             }
             for (parent, expr_id, gr_expr_id, node) in nodes {
-                let ref_id = self.nodes.push(node);
+                let ref_id = self.nodes.push(node.into());
                 if let Some(parent_expr_id) = parent {
                     self.replace_expression(parent_expr_id, expr_id, ref_id)?;
                 } else {
                     match self.get_mut_relation_node(rel_id)? {
-                        Relational::Projection { .. } => {
+                        MutRelational::Projection(_) => {
                             return Err(SbroadError::Invalid(
                                 Entity::Plan,
                                 Some(format_smolstr!(
@@ -1513,7 +1523,7 @@ impl Plan {
                                 )),
                             ))
                         }
-                        Relational::Having { filter, .. } => {
+                        MutRelational::Having(Having { filter, .. }) => {
                             *filter = ref_id;
                         }
                         _ => {
@@ -1575,8 +1585,8 @@ impl Plan {
                 // Projection node is the top node in finals: its aliases
                 // must not be changed (because those are user aliases), so
                 // nothing to do here
-                Relational::Projection { .. } => {}
-                Relational::Having { children, .. } => {
+                Relational::Projection(_) => {}
+                Relational::Having(Having { children, .. }) => {
                     let child_id = *children.first().ok_or_else(|| {
                         SbroadError::Invalid(
                             Entity::Node,
@@ -1608,7 +1618,7 @@ impl Plan {
         }
         for (parent, infos) in parent_to_infos {
             let child_id = {
-                let children = self.get_relation_node(parent)?.children();
+                let children = self.get_relational_children(parent)?;
                 *children.get(0).ok_or_else(|| {
                     SbroadError::Invalid(
                         Entity::Node,
@@ -1661,7 +1671,7 @@ impl Plan {
         let proj_id = *finals.first().ok_or_else(|| {
             SbroadError::Invalid(Entity::Plan, Some("no nodes in Reduce stage!".into()))
         })?;
-        if let Relational::Projection { .. } = self.get_relation_node(proj_id)? {
+        if let Relational::Projection(_) = self.get_relation_node(proj_id)? {
         } else {
             return Err(SbroadError::Invalid(
                 Entity::Plan,
@@ -1680,7 +1690,7 @@ impl Plan {
             self.set_dist(self.get_relational_output(proj_id)?, Distribution::Single)?;
         } else {
             // we have GroupBy, then finals_child_id is final GroupBy
-            let child_id = if let Relational::GroupBy { children, .. } =
+            let child_id = if let Relational::GroupBy(GroupBy { children, .. }) =
                 self.get_relation_node(motion_parent)?
             {
                 *children.first().ok_or_else(|| {
@@ -1747,7 +1757,8 @@ impl Plan {
         if !grouping_exprs.is_empty() {
             // let shard_col_info = self.track_shard_column_pos(final_proj_id)?;
             for expr_id in &grouping_exprs {
-                let Expression::Reference { position, .. } = self.get_expression_node(*expr_id)?
+                let Expression::Reference(Reference { position, .. }) =
+                    self.get_expression_node(*expr_id)?
                 else {
                     continue;
                 };
@@ -1783,14 +1794,14 @@ impl Plan {
         // skip Projection
         for node_id in finals.iter().skip(1).rev() {
             self.set_distribution(self.get_relational_output(*node_id)?)?;
-            if let Relational::Having { .. } = self.get_relation_node(*node_id)? {
+            if let Relational::Having(_) = self.get_relation_node(*node_id)? {
                 having_id = Some(*node_id);
             }
         }
 
         if matches!(
             self.get_relation_node(finals_child_id)?,
-            Relational::GroupBy { .. }
+            Relational::GroupBy(_)
         ) {
             self.set_distribution(self.get_relational_output(final_proj_id)?)?;
         } else {
@@ -1802,7 +1813,7 @@ impl Plan {
 
         // resolve subquery conflicts in HAVING
         if let Some(having_id) = having_id {
-            if let Relational::Having { filter, .. } = self.get_relation_node(having_id)? {
+            if let Relational::Having(Having { filter, .. }) = self.get_relation_node(having_id)? {
                 let strategy = self.resolve_sub_query_conflicts(having_id, *filter)?;
                 self.create_motion_nodes(strategy)?;
             }
diff --git a/sbroad-core/src/ir/transformation/redistribution/left_join.rs b/sbroad-core/src/ir/transformation/redistribution/left_join.rs
index 546f72040..358ec9874 100644
--- a/sbroad-core/src/ir/transformation/redistribution/left_join.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/left_join.rs
@@ -7,8 +7,8 @@ use crate::{
     errors::{Entity, SbroadError},
     ir::{
         distribution::Distribution,
-        expression::NodeId,
-        operator::{JoinKind, Relational},
+        node::{relational::MutRelational, Join, NodeId},
+        operator::JoinKind,
         Plan,
     },
 };
@@ -34,7 +34,7 @@ impl Plan {
             return Ok(None);
         }
 
-        if let Relational::Join { kind, .. } = self.get_mut_relation_node(join_id)? {
+        if let MutRelational::Join(Join { kind, .. }) = self.get_mut_relation_node(join_id)? {
             *kind = JoinKind::Inner;
         }
         self.set_distribution(self.get_relational_output(join_id)?)?;
@@ -58,7 +58,6 @@ impl Plan {
         let outer_child_motion_id = {
             let child = self.get_relation_node(outer_id)?;
             let mut motion_child_id = None;
-
             // Check if there is already motion under outer child
             if child.is_subquery_or_cte() {
                 let sq_child = self.get_relational_child(outer_id, 0)?;
diff --git a/sbroad-core/src/ir/transformation/redistribution/tests.rs b/sbroad-core/src/ir/transformation/redistribution/tests.rs
index 9a5ef710d..ce8c407b1 100644
--- a/sbroad-core/src/ir/transformation/redistribution/tests.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/tests.rs
@@ -1,5 +1,5 @@
 use super::*;
-use crate::ir::operator::Relational;
+use crate::ir::node::Motion;
 use crate::ir::transformation::helpers::sql_to_ir;
 use crate::ir::Plan;
 use crate::ir::Slices;
@@ -69,7 +69,7 @@ fn join_inner_sq_less_for_keys() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -87,7 +87,7 @@ fn join_inner_sq_eq_no_keys() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -105,7 +105,7 @@ fn join_inner_sq_eq_no_outer_keys() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -124,7 +124,7 @@ fn inner_join_full_policy_sq_in_filter() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -184,7 +184,7 @@ fn join_inner_or_local_full_policies() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
diff --git a/sbroad-core/src/ir/transformation/redistribution/tests/between.rs b/sbroad-core/src/ir/transformation/redistribution/tests/between.rs
index 895f6ed26..da8f98d56 100644
--- a/sbroad-core/src/ir/transformation/redistribution/tests/between.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/tests/between.rs
@@ -1,4 +1,5 @@
-use crate::ir::operator::Relational;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::Motion;
 use crate::ir::transformation::helpers::sql_to_ir;
 use crate::ir::transformation::redistribution::tests::get_motion_id;
 use crate::ir::transformation::redistribution::MotionPolicy;
@@ -16,7 +17,7 @@ fn between1() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -46,7 +47,7 @@ fn between3() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
diff --git a/sbroad-core/src/ir/transformation/redistribution/tests/except.rs b/sbroad-core/src/ir/transformation/redistribution/tests/except.rs
index ae827e239..07529547f 100644
--- a/sbroad-core/src/ir/transformation/redistribution/tests/except.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/tests/except.rs
@@ -1,10 +1,11 @@
-use crate::ir::operator::Relational;
 use crate::ir::transformation::helpers::sql_to_ir;
 use crate::ir::transformation::redistribution::tests::get_motion_id;
 use crate::ir::transformation::redistribution::{Key, MotionPolicy};
 use crate::ir::Slices;
 use pretty_assertions::assert_eq;
 
+use super::{Motion, Relational};
+
 #[test]
 fn except1() {
     let query = r#"SELECT 1, 2 FROM "hash_testing" AS "t"
@@ -15,7 +16,7 @@ fn except1() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -43,7 +44,7 @@ fn except3() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -72,7 +73,7 @@ fn except4() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -101,7 +102,7 @@ fn except5() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
diff --git a/sbroad-core/src/ir/transformation/redistribution/tests/not_in.rs b/sbroad-core/src/ir/transformation/redistribution/tests/not_in.rs
index 8593501b9..c77a9b11b 100644
--- a/sbroad-core/src/ir/transformation/redistribution/tests/not_in.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/tests/not_in.rs
@@ -1,10 +1,11 @@
-use crate::ir::operator::Relational;
 use crate::ir::transformation::helpers::sql_to_ir;
 use crate::ir::transformation::redistribution::tests::{get_motion_id, NodeId};
 use crate::ir::transformation::redistribution::MotionPolicy;
 use crate::ir::{ArenaType, Slice, Slices};
 use pretty_assertions::assert_eq;
 
+use super::{Motion, Relational};
+
 #[test]
 fn not_in1() {
     let query = r#"SELECT 1 FROM "hash_testing" AS "t" WHERE "product_code" NOT IN (
@@ -14,7 +15,7 @@ fn not_in1() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -31,8 +32,8 @@ fn not_in2() {
     assert_eq!(
         Slices::from(vec![Slice {
             slice: vec![NodeId {
-                offset: 65,
-                arena_type: ArenaType::Default,
+                offset: 0,
+                arena_type: ArenaType::Arena136,
             }]
         }]),
         plan.slices
@@ -48,7 +49,7 @@ fn not_in3() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -64,7 +65,7 @@ fn not_in4() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
@@ -80,7 +81,7 @@ fn not_in5() {
     plan.add_motions().unwrap();
     let motion_id = *get_motion_id(&plan, 0, 0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(*policy, MotionPolicy::Full);
     } else {
         panic!("Expected a motion node");
diff --git a/sbroad-core/src/ir/transformation/redistribution/tests/segment.rs b/sbroad-core/src/ir/transformation/redistribution/tests/segment.rs
index a42085758..a1819b234 100644
--- a/sbroad-core/src/ir/transformation/redistribution/tests/segment.rs
+++ b/sbroad-core/src/ir/transformation/redistribution/tests/segment.rs
@@ -1,14 +1,15 @@
 use crate::collection;
 use crate::ir::distribution::{Distribution, Key};
 use crate::ir::helpers::RepeatableState;
-use crate::ir::operator::Relational;
+use crate::ir::node::{Node64, NodeId};
 use crate::ir::relation::Column;
 use crate::ir::transformation::helpers::sql_to_ir;
 use crate::ir::transformation::redistribution::{MotionKey, MotionPolicy, Target};
-use crate::ir::Node;
 use pretty_assertions::assert_eq;
 use std::collections::HashSet;
 
+use super::{Motion, Relational};
+
 #[test]
 fn inner_join1() {
     let query = r#"SELECT * FROM "hash_testing" AS "t1"
@@ -20,7 +21,7 @@ fn inner_join1() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -49,7 +50,7 @@ fn inner_join2() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -105,7 +106,7 @@ fn inner_join3() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment((Key { positions: vec![0] }).into())
@@ -115,13 +116,17 @@ fn inner_join3() {
     }
 
     // Check distribution of the join output tuple.
-    let mut join_node: Option<&Relational> = None;
-    for node in &plan.nodes.arena {
-        if let Node::Relational(rel) = node {
-            if matches!(rel, Relational::Join { .. }) {
-                join_node = Some(rel);
-                break;
-            }
+    let mut join_node = None;
+    for node in plan.nodes.arena64.iter().enumerate() {
+        if let Node64::Join(_) = node.1 {
+            join_node = Some(
+                plan.get_relation_node(NodeId {
+                    offset: u32::try_from(node.0).unwrap(),
+                    arena_type: crate::ir::node::ArenaType::Arena64,
+                })
+                .unwrap(),
+            );
+            break;
         }
     }
     let join = join_node.unwrap();
@@ -140,7 +145,7 @@ fn inner_join4() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -163,7 +168,7 @@ fn insert1() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -186,7 +191,7 @@ fn insert2() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::LocalSegment(
@@ -209,7 +214,7 @@ fn insert3() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::LocalSegment(
@@ -232,7 +237,7 @@ fn insert4() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(
@@ -255,7 +260,7 @@ fn insert5() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(MotionKey {
@@ -275,7 +280,7 @@ fn insert6() {
     plan.add_motions().unwrap();
     let motion_id = *plan.slices.slice(0).unwrap().position(0).unwrap();
     let motion = plan.get_relation_node(motion_id).unwrap();
-    if let Relational::Motion { policy, .. } = motion {
+    if let Relational::Motion(Motion { policy, .. }) = motion {
         assert_eq!(
             *policy,
             MotionPolicy::Segment(MotionKey {
diff --git a/sbroad-core/src/ir/transformation/split_columns.rs b/sbroad-core/src/ir/transformation/split_columns.rs
index 4b9cfe655..b0281fc28 100644
--- a/sbroad-core/src/ir/transformation/split_columns.rs
+++ b/sbroad-core/src/ir/transformation/split_columns.rs
@@ -13,7 +13,8 @@
 //! ```
 
 use crate::errors::{Entity, SbroadError};
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{BoolExpr, NodeId, Row};
 use crate::ir::operator::Bool;
 use crate::ir::transformation::OldNewTopIdPair;
 use crate::ir::Plan;
@@ -54,9 +55,9 @@ impl Plan {
     fn split_bool(&mut self, top_id: NodeId) -> Result<OldNewTopIdPair, SbroadError> {
         let top_expr = self.get_expression_node(top_id)?;
         let (left_id, right_id, op) = match top_expr {
-            Expression::Bool {
+            Expression::Bool(BoolExpr {
                 left, op, right, ..
-            } => (*left, *right, op.clone()),
+            }) => (*left, *right, op.clone()),
             _ => {
                 return Err(SbroadError::Invalid(
                     Entity::Expression,
@@ -66,15 +67,15 @@ impl Plan {
                 ));
             }
         };
-        let left_expr = self.get_expression_node(left_id)?;
-        let right_expr = self.get_expression_node(right_id)?;
+        let left_expr = &self.get_expression_node(left_id)?;
+        let right_expr = &self.get_expression_node(right_id)?;
         if let (
-            Expression::Row {
+            Expression::Row(Row {
                 list: left_list, ..
-            },
-            Expression::Row {
+            }),
+            Expression::Row(Row {
                 list: right_list, ..
-            },
+            }),
         ) = (left_expr, right_expr)
         {
             if left_list.len() != right_list.len() {
diff --git a/sbroad-core/src/ir/transformation/split_columns/tests.rs b/sbroad-core/src/ir/transformation/split_columns/tests.rs
index 0c038731c..2bc461321 100644
--- a/sbroad-core/src/ir/transformation/split_columns/tests.rs
+++ b/sbroad-core/src/ir/transformation/split_columns/tests.rs
@@ -53,8 +53,8 @@ fn split_columns3() {
             "{} {} {} {}",
             r#"unexpected number of values:"#,
             r#"left and right rows have different number of columns:"#,
-            r#"Row { list: [NodeId { offset: 12, arena_type: Default }, NodeId { offset: 13, arena_type: Default }, NodeId { offset: 14, arena_type: Default }], distribution: None },"#,
-            r#"Row { list: [NodeId { offset: 15, arena_type: Default }, NodeId { offset: 16, arena_type: Default }], distribution: None }"#,
+            r#"Row(Row { list: [NodeId { offset: 7, arena_type: Arena64 }, NodeId { offset: 8, arena_type: Arena64 }, NodeId { offset: 9, arena_type: Arena64 }], distribution: None }),"#,
+            r#"Row(Row { list: [NodeId { offset: 10, arena_type: Arena64 }, NodeId { offset: 11, arena_type: Arena64 }], distribution: None })"#,
         ),
         format!("{plan_err}")
     );
diff --git a/sbroad-core/src/ir/tree.rs b/sbroad-core/src/ir/tree.rs
index 761135770..245de3521 100644
--- a/sbroad-core/src/ir/tree.rs
+++ b/sbroad-core/src/ir/tree.rs
@@ -1,8 +1,8 @@
 //! IR tree traversal module.
 
-use super::{
-    expression::{Expression, NodeId},
-    Nodes, Plan,
+use super::{node::expression::Expression, Nodes, Plan};
+use crate::ir::node::{
+    Alias, ArithmeticExpr, BoolExpr, Case, Cast, Concat, ExprInParentheses, NodeId, Trim, UnaryExpr,
 };
 use std::cell::RefCell;
 
@@ -11,10 +11,10 @@ trait TreeIterator<'nodes> {
     fn get_child(&self) -> &RefCell<usize>;
     fn get_nodes(&self) -> &'nodes Nodes;
 
-    fn handle_trim(&mut self, expr: &'nodes Expression) -> Option<&'nodes NodeId> {
-        let Expression::Trim {
+    fn handle_trim(&mut self, expr: Expression) -> Option<NodeId> {
+        let Expression::Trim(Trim {
             pattern, target, ..
-        } = expr
+        }) = expr
         else {
             panic!("Trim expected")
         };
@@ -23,61 +23,58 @@ trait TreeIterator<'nodes> {
             0 => {
                 *self.get_child().borrow_mut() += 1;
                 match pattern {
-                    Some(_) => pattern.as_ref(),
-                    None => Some(target),
+                    Some(_) => *pattern,
+                    None => Some(*target),
                 }
             }
             1 => {
                 *self.get_child().borrow_mut() += 1;
-                match pattern {
-                    Some(_) => Some(target),
-                    None => None,
-                }
+                pattern.as_ref().map(|_| *target)
             }
             _ => None,
         }
     }
 
-    fn handle_left_right_children(&mut self, expr: &'nodes Expression) -> Option<&'nodes NodeId> {
-        let (Expression::Bool { left, right, .. }
-        | Expression::Arithmetic { left, right, .. }
-        | Expression::Concat { left, right, .. }) = expr
+    fn handle_left_right_children(&mut self, expr: Expression) -> Option<NodeId> {
+        let (Expression::Bool(BoolExpr { left, right, .. })
+        | Expression::Arithmetic(ArithmeticExpr { left, right, .. })
+        | Expression::Concat(Concat { left, right, .. })) = expr
         else {
             panic!("Expected expression with left and right children")
         };
         let child_step = *self.get_child().borrow();
         if child_step == 0 {
             *self.get_child().borrow_mut() += 1;
-            return Some(left);
+            return Some(*left);
         } else if child_step == 1 {
             *self.get_child().borrow_mut() += 1;
-            return Some(right);
+            return Some(*right);
         }
         None
     }
 
-    fn handle_single_child(&mut self, expr: &'nodes Expression) -> Option<&'nodes NodeId> {
-        let (Expression::Alias { child, .. }
-        | Expression::ExprInParentheses { child }
-        | Expression::Cast { child, .. }
-        | Expression::Unary { child, .. }) = expr
+    fn handle_single_child(&mut self, expr: Expression) -> Option<NodeId> {
+        let (Expression::Alias(Alias { child, .. })
+        | Expression::ExprInParentheses(ExprInParentheses { child })
+        | Expression::Cast(Cast { child, .. })
+        | Expression::Unary(UnaryExpr { child, .. })) = expr
         else {
             panic!("Expected expression with single child")
         };
         let step = *self.get_child().borrow();
         *self.get_child().borrow_mut() += 1;
         if step == 0 {
-            return Some(child);
+            return Some(*child);
         }
         None
     }
 
-    fn handle_case_iter(&mut self, expr: &'nodes Expression) -> Option<&'nodes NodeId> {
-        let Expression::Case {
+    fn handle_case_iter(&mut self, expr: Expression) -> Option<NodeId> {
+        let Expression::Case(Case {
             search_expr,
             when_blocks,
             else_expr,
-        } = expr
+        }) = expr
         else {
             panic!("Case expression expected");
         };
@@ -85,7 +82,7 @@ trait TreeIterator<'nodes> {
         *self.get_child().borrow_mut() += 1;
         if let Some(search_expr) = search_expr {
             if child_step == 0 {
-                return Some(search_expr);
+                return Some(*search_expr);
             }
             child_step -= 1;
         }
@@ -97,16 +94,12 @@ trait TreeIterator<'nodes> {
                 .get(when_blocks_index)
                 .expect("When block must have been found.");
             return match index_reminder {
-                0 => Some(cond_expr),
-                1 => Some(res_expr),
+                0 => Some(*cond_expr),
+                1 => Some(*res_expr),
                 _ => unreachable!("Impossible reminder"),
             };
         } else if when_blocks_index == when_blocks.len() && index_reminder == 0 {
-            if let Some(else_expr) = else_expr {
-                Some(else_expr)
-            } else {
-                None
-            }
+            else_expr.as_ref().copied()
         } else {
             None
         };
diff --git a/sbroad-core/src/ir/tree/and.rs b/sbroad-core/src/ir/tree/and.rs
index c3cfc3167..58940707d 100644
--- a/sbroad-core/src/ir/tree/and.rs
+++ b/sbroad-core/src/ir/tree/and.rs
@@ -1,7 +1,8 @@
 use std::cell::RefCell;
 
 use super::TreeIterator;
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{BoolExpr, NodeId};
 use crate::ir::operator::Bool;
 use crate::ir::{Node, Nodes};
 
@@ -55,9 +56,9 @@ impl<'n> Iterator for AndIterator<'n> {
 
 fn and_next<'nodes>(iter: &mut impl AndTreeIterator<'nodes>) -> Option<&'nodes NodeId> {
     let node = iter.get_nodes().get(iter.get_current());
-    if let Some(Node::Expression(Expression::Bool {
+    if let Some(Node::Expression(Expression::Bool(BoolExpr {
         left, op, right, ..
-    })) = node
+    }))) = node
     {
         if *op != Bool::And {
             return None;
diff --git a/sbroad-core/src/ir/tree/expression.rs b/sbroad-core/src/ir/tree/expression.rs
index faaac377a..5d708111b 100644
--- a/sbroad-core/src/ir/tree/expression.rs
+++ b/sbroad-core/src/ir/tree/expression.rs
@@ -1,7 +1,8 @@
 use std::cell::RefCell;
 
 use super::TreeIterator;
-use crate::ir::expression::{Expression, NodeId};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{NodeId, Row, StableFunction};
 use crate::ir::{Node, Nodes};
 
 trait ExpressionTreeIterator<'nodes>: TreeIterator<'nodes> {
@@ -39,13 +40,15 @@ impl<'n> Nodes {
 
     #[must_use]
     pub fn aggregate_iter(&'n self, current: NodeId, make_row_leaf: bool) -> AggregateIterator<'n> {
-        let must_stop = if let Some(Node::Expression(Expression::StableFunction { name, .. })) =
-            self.get(current)
-        {
-            Expression::is_aggregate_name(name)
-        } else {
-            false
-        };
+        let must_stop =
+            if let Some(Node::Expression(Expression::StableFunction(StableFunction {
+                name, ..
+            }))) = self.get(current)
+            {
+                Expression::is_aggregate_name(name)
+            } else {
+                false
+            };
         AggregateIterator {
             inner: ExpressionIterator {
                 current,
@@ -82,7 +85,7 @@ impl<'n> Iterator for ExpressionIterator<'n> {
     type Item = NodeId;
 
     fn next(&mut self) -> Option<Self::Item> {
-        expression_next(self).copied()
+        expression_next(self)
     }
 }
 
@@ -93,14 +96,12 @@ impl<'n> Iterator for AggregateIterator<'n> {
         if self.must_stop {
             return None;
         }
-        expression_next(&mut self.inner).copied()
+        expression_next(&mut self.inner)
     }
 }
 
 #[allow(clippy::too_many_lines)]
-fn expression_next<'nodes>(
-    iter: &mut impl ExpressionTreeIterator<'nodes>,
-) -> Option<&'nodes NodeId> {
+fn expression_next<'nodes>(iter: &mut impl ExpressionTreeIterator<'nodes>) -> Option<NodeId> {
     let node = iter.get_nodes().get(iter.get_current());
     match node {
         Some(node) => {
@@ -114,7 +115,7 @@ fn expression_next<'nodes>(
                         Expression::Bool { .. }
                         | Expression::Arithmetic { .. }
                         | Expression::Concat { .. } => iter.handle_left_right_children(expr),
-                        Expression::Row { list, .. } => {
+                        Expression::Row(Row { list, .. }) => {
                             let child_step = *iter.get_child().borrow();
                             let mut is_leaf = false;
 
@@ -142,20 +143,20 @@ fn expression_next<'nodes>(
                                     None => return None,
                                     Some(child) => {
                                         *iter.get_child().borrow_mut() += 1;
-                                        return Some(child);
+                                        return Some(*child);
                                     }
                                 }
                             }
 
                             None
                         }
-                        Expression::StableFunction { children, .. } => {
+                        Expression::StableFunction(StableFunction { children, .. }) => {
                             let child_step = *iter.get_child().borrow();
                             match children.get(child_step) {
                                 None => None,
                                 Some(child) => {
                                     *iter.get_child().borrow_mut() += 1;
-                                    Some(child)
+                                    Some(*child)
                                 }
                             }
                         }
@@ -163,14 +164,15 @@ fn expression_next<'nodes>(
                         Expression::Case { .. } => iter.handle_case_iter(expr),
                         Expression::Constant { .. }
                         | Expression::Reference { .. }
-                        | Expression::CountAsterisk => None,
+                        | Expression::CountAsterisk { .. } => None,
                     }
                 }
                 Node::Acl(_)
                 | Node::Block(_)
                 | Node::Ddl(_)
                 | Node::Relational(_)
-                | Node::Parameter(..) => None,
+                | Node::Invalid(_)
+                | Node::Parameter(_) => None,
             }
         }
         None => None,
diff --git a/sbroad-core/src/ir/tree/relation.rs b/sbroad-core/src/ir/tree/relation.rs
index cc14584d1..6399b3097 100644
--- a/sbroad-core/src/ir/tree/relation.rs
+++ b/sbroad-core/src/ir/tree/relation.rs
@@ -1,9 +1,9 @@
 use std::cell::RefCell;
 
 use super::TreeIterator;
-use crate::ir::expression::NodeId;
-use crate::ir::operator::Relational;
-use crate::ir::{ArenaType, Node, Nodes};
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{ArenaType, GroupBy, Limit, NodeId, OrderBy, ScanCte};
+use crate::ir::{Node, Nodes};
 
 trait RelationalTreeIterator<'nodes>: TreeIterator<'nodes> {}
 
@@ -30,7 +30,7 @@ impl<'n> Nodes {
     #[must_use]
     pub fn empty_rel_iter(&'n self) -> RelationalIterator<'n> {
         RelationalIterator {
-            current: self.next_id(ArenaType::Default),
+            current: self.next_id(ArenaType::Arena64),
             child: RefCell::new(0),
             nodes: self,
         }
@@ -57,13 +57,11 @@ impl<'n> Iterator for RelationalIterator<'n> {
     type Item = NodeId;
 
     fn next(&mut self) -> Option<Self::Item> {
-        relational_next(self).copied()
+        relational_next(self)
     }
 }
 
-fn relational_next<'nodes>(
-    iter: &mut impl RelationalTreeIterator<'nodes>,
-) -> Option<&'nodes NodeId> {
+fn relational_next<'nodes>(iter: &mut impl RelationalTreeIterator<'nodes>) -> Option<NodeId> {
     match iter.get_nodes().get(iter.get_current()) {
         Some(Node::Relational(
             node @ (Relational::Except { .. }
@@ -86,27 +84,27 @@ fn relational_next<'nodes>(
             let children = node.children();
             if step < children.len() {
                 *iter.get_child().borrow_mut() += 1;
-                return children.get(step);
+                return children.get(step).copied();
             }
             None
         }
-        Some(Node::Relational(Relational::GroupBy { children, .. })) => {
+        Some(Node::Relational(Relational::GroupBy(GroupBy { children, .. }))) => {
             let step = *iter.get_child().borrow();
             if step == 0 {
                 *iter.get_child().borrow_mut() += 1;
-                return children.get(step);
+                return children.get(step).copied();
             }
             None
         }
         Some(Node::Relational(
-            Relational::OrderBy { child, .. }
-            | Relational::ScanCte { child, .. }
-            | Relational::Limit { child, .. },
+            Relational::OrderBy(OrderBy { child, .. })
+            | Relational::ScanCte(ScanCte { child, .. })
+            | Relational::Limit(Limit { child, .. }),
         )) => {
             let step = *iter.get_child().borrow();
             if step == 0 {
                 *iter.get_child().borrow_mut() += 1;
-                return Some(child);
+                return Some(*child);
             }
             None
         }
@@ -114,6 +112,7 @@ fn relational_next<'nodes>(
             Node::Relational(Relational::ScanRelation { .. })
             | Node::Expression(_)
             | Node::Parameter(_)
+            | Node::Invalid(_)
             | Node::Ddl(_)
             | Node::Acl(_)
             | Node::Block(_),
diff --git a/sbroad-core/src/ir/tree/subtree.rs b/sbroad-core/src/ir/tree/subtree.rs
index 4612eae7d..fa3421862 100644
--- a/sbroad-core/src/ir/tree/subtree.rs
+++ b/sbroad-core/src/ir/tree/subtree.rs
@@ -2,8 +2,14 @@ use std::cell::RefCell;
 use std::cmp::Ordering;
 
 use super::{PlanTreeIterator, Snapshot, TreeIterator};
-use crate::ir::expression::{Expression, NodeId};
-use crate::ir::operator::{OrderByElement, OrderByEntity, Relational};
+use crate::ir::node::expression::Expression;
+use crate::ir::node::relational::Relational;
+use crate::ir::node::{
+    Delete, Except, GroupBy, Having, Insert, Intersect, Join, Limit, Motion, NodeId, OrderBy,
+    Projection, Row, ScanCte, ScanRelation, ScanSubQuery, Selection, StableFunction, Union,
+    UnionAll, Update, Values, ValuesRow,
+};
+use crate::ir::operator::{OrderByElement, OrderByEntity};
 use crate::ir::{Node, Nodes, Plan};
 
 trait SubtreePlanIterator<'plan>: PlanTreeIterator<'plan> {
@@ -55,7 +61,7 @@ impl<'plan> Iterator for SubtreeIterator<'plan> {
     type Item = NodeId;
 
     fn next(&mut self) -> Option<Self::Item> {
-        subtree_next(self, &Snapshot::Latest).copied()
+        subtree_next(self, &Snapshot::Latest)
     }
 }
 
@@ -116,7 +122,7 @@ impl<'plan> Iterator for FlashbackSubtreeIterator<'plan> {
     type Item = NodeId;
 
     fn next(&mut self) -> Option<Self::Item> {
-        subtree_next(self, &Snapshot::Oldest).copied()
+        subtree_next(self, &Snapshot::Oldest)
     }
 }
 
@@ -173,7 +179,7 @@ impl<'plan> Iterator for ExecPlanSubtreeIterator<'plan> {
     type Item = NodeId;
 
     fn next(&mut self) -> Option<Self::Item> {
-        subtree_next(self, &Snapshot::Oldest).copied()
+        subtree_next(self, &Snapshot::Oldest)
     }
 }
 
@@ -192,10 +198,14 @@ impl<'plan> Plan {
 fn subtree_next<'plan>(
     iter: &mut impl SubtreePlanIterator<'plan>,
     snapshot: &Snapshot,
-) -> Option<&'plan NodeId> {
+) -> Option<NodeId> {
     if let Some(child) = iter.get_nodes().get(iter.get_current()) {
         return match child {
-            Node::Parameter(..) | Node::Ddl(..) | Node::Acl(..) | Node::Block(..) => None,
+            Node::Invalid(..)
+            | Node::Parameter(..)
+            | Node::Ddl(..)
+            | Node::Acl(..)
+            | Node::Block(..) => None,
             Node::Expression(expr) => match expr {
                 Expression::Alias { .. }
                 | Expression::ExprInParentheses { .. }
@@ -206,18 +216,18 @@ fn subtree_next<'plan>(
                 | Expression::Arithmetic { .. }
                 | Expression::Concat { .. } => iter.handle_left_right_children(expr),
                 Expression::Trim { .. } => iter.handle_trim(expr),
-                Expression::Row { list, .. }
-                | Expression::StableFunction { children: list, .. } => {
+                Expression::Row(Row { list, .. })
+                | Expression::StableFunction(StableFunction { children: list, .. }) => {
                     let child_step = *iter.get_child().borrow();
                     return match list.get(child_step) {
                         None => None,
                         Some(child) => {
                             *iter.get_child().borrow_mut() += 1;
-                            Some(child)
+                            Some(*child)
                         }
                     };
                 }
-                Expression::Constant { .. } | Expression::CountAsterisk => None,
+                Expression::Constant { .. } | Expression::CountAsterisk { .. } => None,
                 Expression::Reference { .. } => {
                     let step = *iter.get_child().borrow();
                     if step == 0 {
@@ -242,21 +252,21 @@ fn subtree_next<'plan>(
                                     let parent = iter.get_plan().get_relation_node(parent_id);
                                     let mut is_additional = false;
                                     if let Ok(
-                                        Relational::Selection { children, .. }
-                                        | Relational::Having { children, .. },
+                                        Relational::Selection(Selection { children, .. })
+                                        | Relational::Having(Having { children, .. }),
                                     ) = parent
                                     {
                                         if children.iter().skip(1).any(|&c| c == *rel_id) {
                                             is_additional = true;
                                         }
                                     }
-                                    if let Ok(Relational::Join { children, .. }) = parent {
+                                    if let Ok(Relational::Join(Join { children, .. })) = parent {
                                         if children.iter().skip(2).any(|&c| c == *rel_id) {
                                             is_additional = true;
                                         }
                                     }
                                     if is_additional {
-                                        return Some(rel_id);
+                                        return Some(*rel_id);
                                     }
                                 }
                                 _ => {}
@@ -267,24 +277,25 @@ fn subtree_next<'plan>(
                 }
             },
             Node::Relational(r) => match r {
-                Relational::Join {
+                Relational::Join(Join {
                     children,
                     condition,
                     output,
                     ..
-                } => {
+                }) => {
                     let step = *iter.get_child().borrow();
 
                     *iter.get_child().borrow_mut() += 1;
                     match step.cmp(&2) {
                         Ordering::Less => {
-                            return children.get(step);
+                            return children.get(step).copied();
                         }
                         Ordering::Equal => match snapshot {
-                            Snapshot::Latest => Some(condition),
+                            Snapshot::Latest => Some(*condition),
                             Snapshot::Oldest => {
                                 return Some(
-                                    iter.get_plan()
+                                    *iter
+                                        .get_plan()
                                         .undo
                                         .get_oldest(condition)
                                         .map_or_else(|| condition, |id| id),
@@ -293,76 +304,76 @@ fn subtree_next<'plan>(
                         },
                         Ordering::Greater => {
                             if step == 3 && iter.need_output() {
-                                return Some(output);
+                                return Some(*output);
                             }
                             None
                         }
                     }
                 }
 
-                node @ (Relational::Except { output, .. }
-                | Relational::Insert { output, .. }
-                | Relational::Intersect { output, .. }
-                | Relational::Delete { output, .. }
-                | Relational::ScanSubQuery { output, .. }
-                | Relational::Union { output, .. }
-                | Relational::UnionAll { output, .. }) => {
+                ref node @ (Relational::Except(Except { output, .. })
+                | Relational::Insert(Insert { output, .. })
+                | Relational::Intersect(Intersect { output, .. })
+                | Relational::Delete(Delete { output, .. })
+                | Relational::ScanSubQuery(ScanSubQuery { output, .. })
+                | Relational::Union(Union { output, .. })
+                | Relational::UnionAll(UnionAll { output, .. })) => {
                     let step = *iter.get_child().borrow();
                     let children = node.children();
                     if step < children.len() {
                         *iter.get_child().borrow_mut() += 1;
-                        return children.get(step);
+                        return children.get(step).copied();
                     }
                     if iter.need_output() && step == children.len() {
                         *iter.get_child().borrow_mut() += 1;
-                        return Some(output);
+                        return Some(*output);
                     }
                     None
                 }
-                Relational::ScanCte { child, output, .. }
-                | Relational::Limit { child, output, .. } => {
+                Relational::ScanCte(ScanCte { child, output, .. })
+                | Relational::Limit(Limit { child, output, .. }) => {
                     let step = *iter.get_child().borrow();
                     if step == 0 {
                         *iter.get_child().borrow_mut() += 1;
-                        return Some(child);
+                        return Some(*child);
                     }
                     if iter.need_output() && step == 1 {
                         *iter.get_child().borrow_mut() += 1;
-                        return Some(output);
+                        return Some(*output);
                     }
                     None
                 }
-                Relational::GroupBy {
+                Relational::GroupBy(GroupBy {
                     children,
                     output,
                     gr_cols,
                     ..
-                } => {
+                }) => {
                     let step = *iter.get_child().borrow();
                     if step == 0 {
                         *iter.get_child().borrow_mut() += 1;
-                        return children.get(step);
+                        return children.get(step).copied();
                     }
                     let col_idx = step - 1;
                     if col_idx < gr_cols.len() {
                         *iter.get_child().borrow_mut() += 1;
-                        return gr_cols.get(col_idx);
+                        return gr_cols.get(col_idx).copied();
                     }
                     if iter.need_output() && col_idx == gr_cols.len() {
                         *iter.get_child().borrow_mut() += 1;
-                        return Some(output);
+                        return Some(*output);
                     }
                     None
                 }
-                Relational::OrderBy {
+                Relational::OrderBy(OrderBy {
                     child,
                     output,
                     order_by_elements,
-                } => {
+                }) => {
                     let step = *iter.get_child().borrow();
                     if step == 0 {
                         *iter.get_child().borrow_mut() += 1;
-                        return Some(child);
+                        return Some(*child);
                     }
                     let mut col_idx = step - 1;
                     while col_idx < order_by_elements.len() {
@@ -375,83 +386,84 @@ fn subtree_next<'plan>(
                             ..
                         } = current_element
                         {
-                            return Some(expr_id);
+                            return Some(*expr_id);
                         }
                         col_idx += 1;
                     }
                     if iter.need_output() && col_idx == order_by_elements.len() {
                         *iter.get_child().borrow_mut() += 1;
-                        return Some(output);
+                        return Some(*output);
                     }
                     None
                 }
-                Relational::Motion {
+                Relational::Motion(Motion {
                     children,
                     output,
                     policy,
                     ..
-                } => {
+                }) => {
                     if policy.is_local() || iter.need_motion_subtree() {
                         let step = *iter.get_child().borrow();
                         if step < children.len() {
                             *iter.get_child().borrow_mut() += 1;
-                            return children.get(step);
+                            return children.get(step).copied();
                         }
                         if iter.need_output() && step == children.len() {
                             *iter.get_child().borrow_mut() += 1;
-                            return Some(output);
+                            return Some(*output);
                         }
                     } else {
                         let step = *iter.get_child().borrow();
                         if iter.need_output() && step == 0 {
                             *iter.get_child().borrow_mut() += 1;
-                            return Some(output);
+                            return Some(*output);
                         }
                     }
                     None
                 }
-                Relational::Values {
+                Relational::Values(Values {
                     output, children, ..
-                }
-                | Relational::Update {
+                })
+                | Relational::Update(Update {
                     output, children, ..
-                }
-                | Relational::Projection {
+                })
+                | Relational::Projection(Projection {
                     output, children, ..
-                } => {
+                }) => {
                     let step = *iter.get_child().borrow();
                     *iter.get_child().borrow_mut() += 1;
                     if step == 0 {
-                        return Some(output);
+                        return Some(*output);
                     }
                     if step <= children.len() {
-                        return children.get(step - 1);
+                        return children.get(step - 1).copied();
                     }
                     None
                 }
-                Relational::Selection {
+                Relational::Selection(Selection {
                     children,
                     filter,
                     output,
                     ..
-                }
-                | Relational::Having {
+                })
+                | Relational::Having(Having {
                     children,
                     filter,
                     output,
-                } => {
+                }) => {
                     let step = *iter.get_child().borrow();
 
                     *iter.get_child().borrow_mut() += 1;
                     match step.cmp(&1) {
                         Ordering::Less => {
-                            return children.get(step);
+                            return children.get(step).copied();
                         }
                         Ordering::Equal => match snapshot {
-                            Snapshot::Latest => Some(filter),
+                            Snapshot::Latest => Some(*filter),
                             Snapshot::Oldest => {
                                 return Some(
-                                    iter.get_plan()
+                                    *iter
+                                        .get_plan()
                                         .undo
                                         .get_oldest(filter)
                                         .map_or_else(|| filter, |id| id),
@@ -460,31 +472,31 @@ fn subtree_next<'plan>(
                         },
                         Ordering::Greater => {
                             if step == 2 && iter.need_output() {
-                                return Some(output);
+                                return Some(*output);
                             }
                             None
                         }
                     }
                 }
-                Relational::ValuesRow { data, output, .. } => {
+                Relational::ValuesRow(ValuesRow { data, output, .. }) => {
                     let step = *iter.get_child().borrow();
 
                     *iter.get_child().borrow_mut() += 1;
                     if step == 0 {
-                        return Some(data);
+                        return Some(*data);
                     }
                     if iter.need_output() && step == 1 {
-                        return Some(output);
+                        return Some(*output);
                     }
                     None
                 }
-                Relational::ScanRelation { output, .. } => {
+                Relational::ScanRelation(ScanRelation { output, .. }) => {
                     if iter.need_output() {
                         let step = *iter.get_child().borrow();
 
                         *iter.get_child().borrow_mut() += 1;
                         if step == 0 {
-                            return Some(output);
+                            return Some(*output);
                         }
                     }
                     None
diff --git a/sbroad-core/src/ir/tree/tests.rs b/sbroad-core/src/ir/tree/tests.rs
index c188bbcce..4ae29bf83 100644
--- a/sbroad-core/src/ir/tree/tests.rs
+++ b/sbroad-core/src/ir/tree/tests.rs
@@ -1,9 +1,11 @@
+use crate::ir::node::expression::Expression;
+use crate::ir::node::{Alias, ArenaType};
 use crate::ir::operator::Bool;
 use crate::ir::relation::{SpaceEngine, Table, Type};
 use crate::ir::tests::column_user_non_null;
 use crate::ir::tree::traversal::{BreadthFirst, LevelNode, PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use crate::ir::value::Value;
-use crate::ir::{ArenaType, Expression, Plan};
+use crate::ir::Plan;
 use pretty_assertions::assert_eq;
 use smol_str::SmolStr;
 
@@ -221,7 +223,7 @@ fn subtree_dfs_post() {
     .unwrap();
     plan.add_rel(t1);
     let scan_t1_id = plan.add_scan("t1", None).unwrap();
-    let a_ref = plan.nodes.next_id(ArenaType::Default);
+    let a_ref = plan.nodes.next_id(ArenaType::Arena64);
     let a = plan.add_row_from_child(scan_t1_id, &["a"]).unwrap();
     let const1 = plan.add_const(Value::from(1_i64));
     let eq_op = plan.nodes.add_bool(a, Bool::Eq, const1).unwrap();
@@ -240,9 +242,9 @@ fn subtree_dfs_post() {
         .clone_row_list()
         .unwrap();
     let alias_id = row_children.first().unwrap();
-    let Expression::Alias {
+    let Expression::Alias(Alias {
         child: c_ref_id, ..
-    } = plan.get_expression_node(*alias_id).unwrap()
+    }) = plan.get_expression_node(*alias_id).unwrap()
     else {
         panic!("invalid child in the row");
     };
diff --git a/sbroad-core/src/ir/undo.rs b/sbroad-core/src/ir/undo.rs
index 828f5d67c..76a03c955 100644
--- a/sbroad-core/src/ir/undo.rs
+++ b/sbroad-core/src/ir/undo.rs
@@ -3,7 +3,7 @@
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 
-use super::expression::NodeId;
+use crate::ir::node::NodeId;
 
 /// Transformation log keep the history of the plan subtree modifications.
 /// When we modify the plan subtree, we add a new entry to the log, where
-- 
GitLab