From 06d093f4f422866e2a09040c77c15674370c5937 Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Sun, 26 Jan 2025 15:14:43 +0300
Subject: [PATCH 1/5] fix: fix VALUES types calculation, support absence of
 type during its calculation on SQL expressions

Previously we were mistakenly giving default Integer type to the Null value or to the column with no type. This commit fixes such behavior
---
 .../sbroad-cartridge/src/cartridge/config.rs  |   3 +-
 .../src/cartridge/config/tests.rs             |  38 +-
 .../arbitrary_expressions_test.lua            |  26 +-
 .../sbroad-core/src/backend/sql/ir/tests.rs   |   1 +
 .../src/backend/sql/ir/tests/selection.rs     |  24 +-
 sbroad/sbroad-core/src/backend/sql/tree.rs    |   8 +-
 sbroad/sbroad-core/src/cbo/selectivity.rs     |   8 +-
 sbroad/sbroad-core/src/executor.rs            |   2 +-
 sbroad/sbroad-core/src/executor/engine.rs     |  14 +-
 .../src/executor/engine/helpers.rs            |  20 +-
 .../sbroad-core/src/executor/engine/mock.rs   | 712 ++++++++++++++----
 sbroad/sbroad-core/src/executor/result.rs     |  30 +-
 .../sbroad-core/src/executor/result/tests.rs  |   2 -
 sbroad/sbroad-core/src/executor/tests.rs      |  10 +
 .../src/executor/tests/exec_plan.rs           |  43 +-
 sbroad/sbroad-core/src/executor/vtable.rs     | 125 +--
 .../sbroad-core/src/executor/vtable/tests.rs  |  43 +-
 sbroad/sbroad-core/src/frontend/sql.rs        |  26 +-
 .../sbroad-core/src/frontend/sql/ir/tests.rs  |  35 +-
 .../src/frontend/sql/ir/tests/coalesce.rs     |   2 +-
 .../src/frontend/sql/ir/tests/global.rs       |   6 +-
 .../src/frontend/sql/ir/tests/params.rs       |   4 +-
 sbroad/sbroad-core/src/ir.rs                  |   8 +-
 sbroad/sbroad-core/src/ir/aggregates.rs       |  63 +-
 sbroad/sbroad-core/src/ir/api/constant.rs     |  10 +-
 sbroad/sbroad-core/src/ir/api/parameter.rs    |  11 +-
 sbroad/sbroad-core/src/ir/ddl.rs              |  14 +-
 sbroad/sbroad-core/src/ir/distribution.rs     |  16 +-
 sbroad/sbroad-core/src/ir/explain.rs          |  15 +-
 sbroad/sbroad-core/src/ir/expression.rs       |   7 +-
 sbroad/sbroad-core/src/ir/expression/cast.rs  |   4 +-
 sbroad/sbroad-core/src/ir/expression/tests.rs |  34 +-
 sbroad/sbroad-core/src/ir/expression/types.rs | 143 ++--
 sbroad/sbroad-core/src/ir/function.rs         |   8 +-
 sbroad/sbroad-core/src/ir/helpers.rs          |   3 +-
 sbroad/sbroad-core/src/ir/helpers/tests.rs    |  96 +--
 sbroad/sbroad-core/src/ir/node.rs             |   8 +-
 sbroad/sbroad-core/src/ir/operator.rs         |  30 +-
 sbroad/sbroad-core/src/ir/operator/tests.rs   |   7 +-
 sbroad/sbroad-core/src/ir/relation.rs         | 151 ++--
 sbroad/sbroad-core/src/ir/relation/tests.rs   |  21 +-
 sbroad/sbroad-core/src/ir/tests.rs            |  17 +-
 .../ir/transformation/equality_propagation.rs |   4 +-
 .../equality_propagation/tests.rs             |  18 +-
 .../transformation/redistribution/groupby.rs  |  36 +-
 sbroad/sbroad-core/src/ir/value.rs            |  19 +-
 sbroad/sbroad-core/src/ir/value/tests.rs      |   6 +-
 src/pgproto/backend/describe.rs               |  26 +-
 src/sql.rs                                    |  25 +-
 src/sql/router.rs                             |   4 +-
 50 files changed, 1327 insertions(+), 659 deletions(-)

diff --git a/sbroad/sbroad-cartridge/src/cartridge/config.rs b/sbroad/sbroad-cartridge/src/cartridge/config.rs
index 23b80e2a58..44acd34476 100644
--- a/sbroad/sbroad-cartridge/src/cartridge/config.rs
+++ b/sbroad/sbroad-cartridge/src/cartridge/config.rs
@@ -11,6 +11,7 @@ use sbroad::executor::engine::helpers::normalize_name_from_sql;
 use sbroad::executor::engine::{get_builtin_functions, Metadata};
 use sbroad::executor::lru::DEFAULT_CAPACITY;
 use sbroad::ir::function::Function;
+use sbroad::ir::relation::DerivedType;
 use sbroad::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type};
 use sbroad::{debug, warn};
 
@@ -138,7 +139,7 @@ impl RouterConfiguration {
                         } else {
                             ColumnRole::User
                         };
-                        let col = Column::new(name, t, role, is_nullable);
+                        let col = Column::new(name, DerivedType::new(t), role, is_nullable);
                         result.push(col);
                     }
                     result
diff --git a/sbroad/sbroad-cartridge/src/cartridge/config/tests.rs b/sbroad/sbroad-cartridge/src/cartridge/config/tests.rs
index ba4c6c8294..3f7149f4f8 100644
--- a/sbroad/sbroad-cartridge/src/cartridge/config/tests.rs
+++ b/sbroad/sbroad-cartridge/src/cartridge/config/tests.rs
@@ -1,5 +1,6 @@
 use super::*;
 use pretty_assertions::assert_eq;
+use sbroad::ir::relation::DerivedType;
 
 #[test]
 fn test_yaml_schema_parser() {
@@ -145,15 +146,40 @@ fn test_getting_table_segment() {
         vec![
             Column::new(
                 "identification_number",
-                Type::Integer,
+                DerivedType::new(Type::Integer),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("product_code", Type::String, ColumnRole::User, false),
-            Column::new("product_units", Type::Boolean, ColumnRole::User, false),
-            Column::new("sys_op", Type::Integer, ColumnRole::User, false),
-            Column::new("detail", Type::Array, ColumnRole::User, false),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "product_code",
+                DerivedType::new(Type::String),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "product_units",
+                DerivedType::new(Type::Boolean),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "sys_op",
+                DerivedType::new(Type::Integer),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "detail",
+                DerivedType::new(Type::Array),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ],
         &["identification_number", "product_code"],
         &["identification_number"],
diff --git a/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua b/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua
index 98fd6fd6fc..1c278e7b10 100644
--- a/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua
+++ b/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua
@@ -60,6 +60,16 @@ arbitrary_projection.test_arbitrary_invalid = function()
         select "a" + "b" and true from "arithmetic_space"
     ]], {} })
     t.assert_str_contains(tostring(err), "Type mismatch: can not convert integer(3) to boolean")
+
+    local _, err = api:call("sbroad.execute", { [[
+        SELECT
+            CASE "id"
+                WHEN 1 THEN 'first'
+                ELSE 42
+            END "case_result"
+        FROM "arithmetic_space"
+    ]], {} })
+    t.assert_str_contains(tostring(err), "expected string type, but got unsigned")
 end
 
 arbitrary_projection.test_arbitrary_valid = function()
@@ -196,16 +206,16 @@ arbitrary_projection.test_arbitrary_valid = function()
             CASE "id"
                 WHEN 1 THEN 'first'
                 WHEN 2 THEN 'second'
-                ELSE 42
+                ELSE '42'
             END "case_result"
         FROM "arithmetic_space"
     ]], {} })
     t.assert_equals(err, nil)
     t.assert_equals(r.metadata, {
-        {name = "case_result", type = "integer"},
+        {name = "case_result", type = "string"},
     })
     t.assert_items_equals(r.rows, {
-        {'first'}, {42}, {42}, {42}, {42}, {'second'}, {42}, {42}, {42}, {42}
+        {'first'}, {'42'}, {'42'}, {'42'}, {'42'}, {'second'}, {'42'}, {'42'}, {'42'}, {'42'}
     })
 
     local r, err = api:call("sbroad.execute", { [[
@@ -281,14 +291,14 @@ arbitrary_projection.test_arbitrary_valid = function()
         SELECT
             "id",
             CASE
-                WHEN false THEN 'never'
+                WHEN false THEN 0
                 WHEN "id" < 3 THEN 1
                 WHEN "id" > 3 AND "id" < 8 THEN 2
                 ELSE
                     CASE
                         WHEN "id" = 8 THEN 3
                         WHEN "id" = 9 THEN 4
-                        ELSE 0.42
+                        ELSE 0
                     END
             END
         FROM "arithmetic_space"
@@ -296,16 +306,16 @@ arbitrary_projection.test_arbitrary_valid = function()
     t.assert_equals(err, nil)
     t.assert_equals(r.metadata, {
         {name = "id", type = "integer"},
-        {name = "col_1", type = "double"},
+        {name = "col_1", type = "unsigned"},
     })
     t.assert_items_equals(r.rows, {
         {1, 1},
         {5, 2},
         {8, 3},
         {9, 4},
-        {10, 0.42},
+        {10, 0},
         {2, 1},
-        {3, 0.42},
+        {3, 0},
         {4, 2},
         {6, 2},
         {7, 2},
diff --git a/sbroad/sbroad-core/src/backend/sql/ir/tests.rs b/sbroad/sbroad-core/src/backend/sql/ir/tests.rs
index f699ec5196..86c7b7ef22 100644
--- a/sbroad/sbroad-core/src/backend/sql/ir/tests.rs
+++ b/sbroad/sbroad-core/src/backend/sql/ir/tests.rs
@@ -31,6 +31,7 @@ fn check_sql_with_snapshot(
     let nodes = ordered.to_syntax_data().unwrap();
     let (sql, _) = ex_plan.to_sql(&nodes, "test", None).unwrap();
 
+    println!("{}", sql.pattern);
     assert_eq!(expected, sql,);
 }
 
diff --git a/sbroad/sbroad-core/src/backend/sql/ir/tests/selection.rs b/sbroad/sbroad-core/src/backend/sql/ir/tests/selection.rs
index 7e1b97e2c9..9b34bb870f 100644
--- a/sbroad/sbroad-core/src/backend/sql/ir/tests/selection.rs
+++ b/sbroad/sbroad-core/src/backend/sql/ir/tests/selection.rs
@@ -1,4 +1,5 @@
 use super::*;
+use crate::executor::tests::f_sql;
 use crate::ir::tree::Snapshot;
 use crate::ir::value::Value;
 
@@ -65,14 +66,21 @@ fn selection2_latest() {
         AND ("product_units" <> "sys_op" OR "product_units" IS NULL)"#;
 
     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#"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#"and ("hash_testing"."product_units") is null"#
-        ].join(" "),
-        vec![Value::Unsigned(1), Value::Unsigned(1), Value::Unsigned(1), Value::Unsigned(1)],
+        f_sql(
+            r#"SELECT "hash_testing"."product_code" FROM "hash_testing"
+WHERE ("hash_testing"."identification_number", "hash_testing"."product_units", "hash_testing"."identification_number") =
+("hash_testing"."product_units", ?, ?)
+and ("hash_testing"."product_units") <> ("hash_testing"."sys_op")
+or ("hash_testing"."identification_number", "hash_testing"."product_units", "hash_testing"."identification_number")
+= ("hash_testing"."product_units", ?, ?)
+and ("hash_testing"."product_units") is null"#,
+        ),
+        vec![
+            Value::Unsigned(1),
+            Value::Unsigned(1),
+            Value::Unsigned(1),
+            Value::Unsigned(1),
+        ],
     );
     check_sql_with_snapshot(query, vec![], expected, Snapshot::Latest);
 }
diff --git a/sbroad/sbroad-core/src/backend/sql/tree.rs b/sbroad/sbroad-core/src/backend/sql/tree.rs
index ca934e2742..c0306e0c75 100644
--- a/sbroad/sbroad-core/src/backend/sql/tree.rs
+++ b/sbroad/sbroad-core/src/backend/sql/tree.rs
@@ -1010,7 +1010,13 @@ impl<'p> SyntaxPlan<'p> {
                     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})"));
+                    let casted_null_str = if let Some(col_type) = col_type.get() {
+                        format_smolstr!("cast(null as {col_type})")
+                    } else {
+                        // No need to cast as there are no type.
+                        SmolStr::from("null")
+                    };
+                    select_columns.push(casted_null_str);
                 }
                 let empty_select =
                     format_smolstr!("select {} where false", select_columns.join(","));
diff --git a/sbroad/sbroad-core/src/cbo/selectivity.rs b/sbroad/sbroad-core/src/cbo/selectivity.rs
index bc03140105..f6cc2ba748 100644
--- a/sbroad/sbroad-core/src/cbo/selectivity.rs
+++ b/sbroad/sbroad-core/src/cbo/selectivity.rs
@@ -264,6 +264,10 @@ pub fn calculate_filter_selectivity(
         )),
     ));
 
+    let Some(column_type) = column_type.get() else {
+        return types_mismatch_error;
+    };
+
     match column_type {
         Type::Boolean => match constant {
             Value::Boolean(b) => {
@@ -406,8 +410,8 @@ pub fn calculate_condition_selectivity(
         )),
     ));
 
-    match (left_column_type, right_column_type) {
-        (Type::Boolean, Type::Boolean) => {
+    match (left_column_type.get(), right_column_type.get()) {
+        (Some(Type::Boolean), Some(Type::Boolean)) => {
             let left_downcasted_stats =
                 downcast_column_stats::<bool>(&left_column_stats, left_column)?;
             let right_downcasted_stats =
diff --git a/sbroad/sbroad-core/src/executor.rs b/sbroad/sbroad-core/src/executor.rs
index 039a197479..7520a2a1ad 100644
--- a/sbroad/sbroad-core/src/executor.rs
+++ b/sbroad/sbroad-core/src/executor.rs
@@ -427,4 +427,4 @@ where
 }
 
 #[cfg(test)]
-mod tests;
+pub mod tests;
diff --git a/sbroad/sbroad-core/src/executor/engine.rs b/sbroad/sbroad-core/src/executor/engine.rs
index f75e0f0bf4..e1faafc98f 100644
--- a/sbroad/sbroad-core/src/executor/engine.rs
+++ b/sbroad/sbroad-core/src/executor/engine.rs
@@ -22,8 +22,8 @@ use crate::executor::ir::ExecutionPlan;
 use crate::executor::protocol::SchemaInfo;
 use crate::executor::vtable::VirtualTable;
 use crate::ir::function::Function;
-use crate::ir::relation::Table;
 use crate::ir::relation::Type;
+use crate::ir::relation::{DerivedType, Table};
 use crate::ir::value::Value;
 
 use super::result::ProducerResult;
@@ -78,12 +78,12 @@ pub fn get_builtin_functions() -> &'static [Function] {
     unsafe {
         BUILTINS.get_or_init(|| {
             vec![
-                Function::new_stable("to_date".into(), Type::Datetime, false),
-                Function::new_stable("to_char".into(), Type::String, false),
-                Function::new_stable("substr".into(), Type::String, true),
-                Function::new_stable("lower".into(), Type::String, true),
-                Function::new_stable("upper".into(), Type::String, true),
-                Function::new_stable("coalesce".into(), Type::Any, true),
+                Function::new_stable("to_date".into(), DerivedType::new(Type::Datetime), false),
+                Function::new_stable("to_char".into(), DerivedType::new(Type::String), false),
+                Function::new_stable("substr".into(), DerivedType::new(Type::String), true),
+                Function::new_stable("lower".into(), DerivedType::new(Type::String), true),
+                Function::new_stable("upper".into(), DerivedType::new(Type::String), true),
+                Function::new_stable("coalesce".into(), DerivedType::new(Type::Any), true),
             ]
         })
     }
diff --git a/sbroad/sbroad-core/src/executor/engine/helpers.rs b/sbroad/sbroad-core/src/executor/engine/helpers.rs
index 5c08b36e81..4e9ef8bc3c 100644
--- a/sbroad/sbroad-core/src/executor/engine/helpers.rs
+++ b/sbroad/sbroad-core/src/executor/engine/helpers.rs
@@ -3,9 +3,12 @@ use ahash::AHashMap;
 use crate::{
     error,
     executor::vtable::vtable_indexed_column_name,
-    ir::node::{
-        expression::Expression, relational::Relational, Alias, Constant, Limit, Motion, NodeId,
-        Update, Values, ValuesRow,
+    ir::{
+        node::{
+            expression::Expression, relational::Relational, Alias, Constant, Limit, Motion, NodeId,
+            Update, Values, ValuesRow,
+        },
+        relation::DerivedType,
     },
     utils::MutexLike,
 };
@@ -45,10 +48,10 @@ use crate::{
         ir::{ExecutionPlan, QueryType},
         protocol::{Binary, EncodedOptionalData, OptionalData, RequiredData},
         result::{ConsumerResult, MetadataColumn, ProducerResult},
-        vtable::{calculate_vtable_unified_types, VTableTuple, VirtualTable},
+        vtable::{calculate_unified_types, VTableTuple, VirtualTable},
     },
     ir::{
-        relation::{Column, ColumnRole, Type},
+        relation::{Column, ColumnRole},
         transformation::redistribution::{MotionKey, MotionPolicy},
         tree::Snapshot,
         value::Value,
@@ -429,7 +432,7 @@ pub enum TupleBuilderCommand {
     TakePosition(usize),
     /// Take a value from the original tuple and cast
     /// it into specified type.
-    TakeAndCastPosition(usize, Type),
+    TakeAndCastPosition(usize, DerivedType),
     /// Set a specified value.
     /// Related only to the tuple we are currently constructing and not to the original tuple.
     SetValue(Value),
@@ -442,7 +445,7 @@ pub enum TupleBuilderCommand {
     /// Update table column to the value in original tuple on specified position and cast it
     /// into specifeid type.
     /// Needed only for `Update`.
-    UpdateColToCastedPos(usize, usize, Type),
+    UpdateColToCastedPos(usize, usize, DerivedType),
 }
 
 /// Vec of commands that helps us transforming `VTableTuple` into a tuple suitable to be passed
@@ -1046,7 +1049,8 @@ pub fn materialize_values(
             .as_virtual_table(columns)?
     };
 
-    let unified_types = calculate_vtable_unified_types(&vtable)?;
+    let vtable_types = vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types)?;
     vtable.cast_values(&unified_types)?;
 
     let _ = exec_plan.get_mut_ir_plan().replace_with_stub(values_id);
diff --git a/sbroad/sbroad-core/src/executor/engine/mock.rs b/sbroad/sbroad-core/src/executor/engine/mock.rs
index 96cd81f698..f40d6b1f2f 100644
--- a/sbroad/sbroad-core/src/executor/engine/mock.rs
+++ b/sbroad/sbroad-core/src/executor/engine/mock.rs
@@ -32,6 +32,7 @@ use crate::executor::Cache;
 use crate::frontend::sql::ast::AbstractSyntaxTree;
 use crate::ir::function::Function;
 use crate::ir::node::NodeId;
+use crate::ir::relation::DerivedType;
 use crate::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type};
 use crate::ir::tree::Snapshot;
 use crate::ir::value::{LuaValue, Value};
@@ -106,9 +107,11 @@ impl RouterConfigurationMock {
     #[must_use]
     pub fn new() -> Self {
         let name_func = normalize_name_from_sql("func");
-        let fn_func = Function::new_stable(name_func.clone(), Type::Integer, false);
+        let fn_func =
+            Function::new_stable(name_func.clone(), DerivedType::new(Type::Integer), false);
         let name_trim = normalize_name_from_sql("trim");
-        let trim_func = Function::new_stable(name_trim.clone(), Type::String, false);
+        let trim_func =
+            Function::new_stable(name_trim.clone(), DerivedType::new(Type::String), false);
         let mut functions = HashMap::new();
         functions.insert(name_func, fn_func);
         functions.insert(name_trim, trim_func);
@@ -121,14 +124,34 @@ impl RouterConfigurationMock {
         let columns = vec![
             Column::new(
                 "identification_number",
-                Type::Integer,
+                DerivedType::new(Type::Integer),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("product_code", Type::String, ColumnRole::User, false),
-            Column::new("product_units", Type::Boolean, ColumnRole::User, true),
-            Column::new("sys_op", Type::Unsigned, ColumnRole::User, true),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "product_code",
+                DerivedType::new(Type::String),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "product_units",
+                DerivedType::new(Type::Boolean),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "sys_op",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key = &["identification_number", "product_code"];
         let primary_key = &["product_code", "identification_number"];
@@ -182,11 +205,36 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("id", Type::Unsigned, ColumnRole::User, false),
-            Column::new("sysFrom", Type::Unsigned, ColumnRole::User, true),
-            Column::new("FIRST_NAME", Type::String, ColumnRole::User, true),
-            Column::new("sys_op", Type::Unsigned, ColumnRole::User, true),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "sysFrom",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "FIRST_NAME",
+                DerivedType::new(Type::String),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "sys_op",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key = &["id"];
         let primary_key = &["id"];
@@ -216,8 +264,18 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("id", Type::Unsigned, ColumnRole::User, false),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key: &[&str] = &["id"];
         let primary_key: &[&str] = &["id"];
@@ -234,9 +292,24 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("A", Type::Unsigned, ColumnRole::User, true),
-            Column::new("B", Type::Unsigned, ColumnRole::User, false),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "A",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "B",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key: &[&str] = &["A", "B"];
         let primary_key: &[&str] = &["B"];
@@ -253,11 +326,36 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("a", Type::Unsigned, ColumnRole::User, true),
-            Column::new("b", Type::Unsigned, ColumnRole::User, false),
-            Column::new("c", Type::Unsigned, ColumnRole::User, true),
-            Column::new("d", Type::Unsigned, ColumnRole::User, true),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "a",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "b",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "c",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "d",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                true,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key: &[&str] = &["a", "b"];
         let primary_key: &[&str] = &["b"];
@@ -268,9 +366,19 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("a", Type::String, ColumnRole::User, false),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
-            Column::new("b", Type::Integer, ColumnRole::User, false),
+            Column::new("a", DerivedType::new(Type::String), ColumnRole::User, false),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
+            Column::new(
+                "b",
+                DerivedType::new(Type::Integer),
+                ColumnRole::User,
+                false,
+            ),
         ];
         let sharding_key: &[&str] = &["a", "b"];
         let primary_key: &[&str] = &["a", "b"];
@@ -281,11 +389,36 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("e", Type::Unsigned, ColumnRole::User, false),
-            Column::new("f", Type::Unsigned, ColumnRole::User, false),
-            Column::new("g", Type::Unsigned, ColumnRole::User, false),
-            Column::new("h", Type::Unsigned, ColumnRole::User, false),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "e",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "f",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "g",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "h",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key: &[&str] = &["e", "f"];
         let primary_key: &[&str] = &["g", "h"];
@@ -296,9 +429,19 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
-            Column::new("a", Type::String, ColumnRole::User, false),
-            Column::new("b", Type::Integer, ColumnRole::User, false),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
+            Column::new("a", DerivedType::new(Type::String), ColumnRole::User, false),
+            Column::new(
+                "b",
+                DerivedType::new(Type::Integer),
+                ColumnRole::User,
+                false,
+            ),
         ];
         let sharding_key: &[&str] = &["a"];
         let primary_key: &[&str] = &["a"];
@@ -309,9 +452,19 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
-            Column::new("c", Type::String, ColumnRole::User, false),
-            Column::new("d", Type::Integer, ColumnRole::User, false),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
+            Column::new("c", DerivedType::new(Type::String), ColumnRole::User, false),
+            Column::new(
+                "d",
+                DerivedType::new(Type::Integer),
+                ColumnRole::User,
+                false,
+            ),
         ];
         let sharding_key: &[&str] = &["c"];
         let primary_key: &[&str] = &["d"];
@@ -322,8 +475,18 @@ impl RouterConfigurationMock {
         );
 
         let columns = vec![
-            Column::new("a", Type::Integer, ColumnRole::User, false),
-            Column::new("b", Type::Integer, ColumnRole::User, false),
+            Column::new(
+                "a",
+                DerivedType::new(Type::Integer),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "b",
+                DerivedType::new(Type::Integer),
+                ColumnRole::User,
+                false,
+            ),
         ];
         let primary_key: &[&str] = &["a"];
         tables.insert(
@@ -333,365 +496,630 @@ impl RouterConfigurationMock {
 
         // Table for sbroad-benches
         let columns = vec![
-            Column::new("vehicleguid", Type::Unsigned, ColumnRole::User, false),
-            Column::new("reestrid", Type::Unsigned, ColumnRole::User, false),
-            Column::new("reestrstatus", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehicleregno", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclevin", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclevin2", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclechassisnum", Type::Unsigned, ColumnRole::User, false),
+            Column::new(
+                "vehicleguid",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "reestrid",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "reestrstatus",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicleregno",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclevin",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclevin2",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclechassisnum",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
             Column::new(
                 "vehiclereleaseyear",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "operationregdoctypename",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "operationregdoc",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("operationregdoc", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "operationregdocissuedate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "operationregdoccomments",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "vehicleptstypename",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicleptsnum",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehicleptsnum", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehicleptsissuedate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicleptsissuer",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehicleptsissuer", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehicleptscomments",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclebodycolor",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclebrand",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclemodel",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclebrandmodel",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclebodynum",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclecost",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclegasequip",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehiclebodycolor", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclebrand", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclemodel", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclebrandmodel", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclebodynum", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclecost", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclegasequip", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehicleproducername",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclegrossmass",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclemass",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehiclegrossmass", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclemass", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehiclesteeringwheeltypeid",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclekpptype",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehiclekpptype", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehicletransmissiontype",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicletypename",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehiclecategory",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicletypeunit",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicleecoclass",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehicletypename", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehiclecategory", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehicletypeunit", Type::Unsigned, ColumnRole::User, false),
-            Column::new("vehicleecoclass", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehiclespecfuncname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "vehicleenclosedvolume",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "vehicleenginemodel",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicleenginenum",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehicleenginenum", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "vehicleenginepower",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "vehicleenginepowerkw",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "vehicleenginetype",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("vehicleenginetype", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "holdrestrictiondate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "approvalnum",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "approvaldate",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "approvaltype",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("approvalnum", Type::Unsigned, ColumnRole::User, false),
-            Column::new("approvaldate", Type::Unsigned, ColumnRole::User, false),
-            Column::new("approvaltype", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "utilizationfeename",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "customsdoc",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "customsdocdate",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "customsdocissue",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("customsdoc", Type::Unsigned, ColumnRole::User, false),
-            Column::new("customsdocdate", Type::Unsigned, ColumnRole::User, false),
-            Column::new("customsdocissue", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "customsdocrestriction",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "customscountryremovalid",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "customscountryremovalname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerorgname",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerinn",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerogrn",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerkpp",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("ownerorgname", Type::Unsigned, ColumnRole::User, false),
-            Column::new("ownerinn", Type::Unsigned, ColumnRole::User, false),
-            Column::new("ownerogrn", Type::Unsigned, ColumnRole::User, false),
-            Column::new("ownerkpp", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "ownerpersonlastname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "ownerpersonfirstname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "ownerpersonmiddlename",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "ownerpersonbirthdate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerbirthplace",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerpersonogrnip",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "owneraddressindex",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("ownerbirthplace", Type::Unsigned, ColumnRole::User, false),
-            Column::new("ownerpersonogrnip", Type::Unsigned, ColumnRole::User, false),
-            Column::new("owneraddressindex", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "owneraddressmundistrict",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "owneraddresssettlement",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "owneraddressstreet",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerpersoninn",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("ownerpersoninn", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "ownerpersondoccode",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "ownerpersondocnum",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("ownerpersondocnum", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "ownerpersondocdate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "operationname",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "operationdate",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("operationname", Type::Unsigned, ColumnRole::User, false),
-            Column::new("operationdate", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "operationdepartmentname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "operationattorney",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "operationlising",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "holdertypeid",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("operationattorney", Type::Unsigned, ColumnRole::User, false),
-            Column::new("operationlising", Type::Unsigned, ColumnRole::User, false),
-            Column::new("holdertypeid", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "holderpersondoccode",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersondocnum",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersondocdate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersondocissuer",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersonlastname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersonfirstname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersonmiddlename",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersonbirthdate",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderpersonbirthregionid",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "holderpersonsex",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("holderpersonsex", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "holderpersonbirthplace",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "holderpersoninn",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "holderpersonsnils",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("holderpersoninn", Type::Unsigned, ColumnRole::User, false),
-            Column::new("holderpersonsnils", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "holderpersonogrnip",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "holderaddressguid",
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("holderaddressguid", Type::Unsigned, ColumnRole::User, false),
             Column::new(
                 "holderaddressregionid",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressregionname",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressdistrict",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressmundistrict",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddresssettlement",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressstreet",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressbuilding",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressstructureid",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressstructurename",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
             Column::new(
                 "holderaddressstructure",
-                Type::Unsigned,
+                DerivedType::new(Type::Unsigned),
                 ColumnRole::User,
                 false,
             ),
-            Column::new("sys_from", Type::Unsigned, ColumnRole::User, false),
-            Column::new("sys_to", Type::Unsigned, ColumnRole::User, false),
-            Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "sys_from",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "sys_to",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::User,
+                false,
+            ),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ];
         let sharding_key: &[&str] = &["reestrid"];
         let primary_key: &[&str] = &["reestrid"];
diff --git a/sbroad/sbroad-core/src/executor/result.rs b/sbroad/sbroad-core/src/executor/result.rs
index 5a7180c132..212aef4942 100644
--- a/sbroad/sbroad-core/src/executor/result.rs
+++ b/sbroad/sbroad-core/src/executor/result.rs
@@ -20,7 +20,7 @@ use crate::errors::SbroadError;
 use crate::executor::vtable::{VTableTuple, VirtualTable};
 use crate::ir::node::relational::Relational;
 use crate::ir::node::{Node, NodeId};
-use crate::ir::relation::{Column, ColumnRole, Type};
+use crate::ir::relation::{Column, ColumnRole, DerivedType, Type};
 use crate::ir::tree::traversal::{PostOrderWithFilter, REL_CAPACITY};
 use crate::ir::value::{LuaValue, Value};
 use crate::ir::Plan;
@@ -72,7 +72,12 @@ impl TryInto<Column> for &MetadataColumn {
 
     fn try_into(self) -> Result<Column, Self::Error> {
         let col_type = Type::new(&self.r#type)?;
-        Ok(Column::new(&self.name, col_type, ColumnRole::User, true))
+        Ok(Column::new(
+            &self.name,
+            DerivedType::new(col_type),
+            ColumnRole::User,
+            true,
+        ))
     }
 }
 
@@ -151,11 +156,26 @@ impl ProducerResult {
                 let column = &columns[i];
                 let value = Value::from(value);
 
-                if value.get_type() == column.r#type {
+                // TODO: Seems like logic of casting may be removed and replaced with
+                //       `cast_values` call after vtable is built.
+                let Some(column_ty) = column.r#type.get() else {
+                    // No need to cast Null.
                     tuple.push(value);
+                    continue;
+                };
+
+                let types_equal = if let Some(value_type) = value.get_type().get() {
+                    value_type == column_ty
+                } else {
+                    false
+                };
+
+                let casted_value = if types_equal {
+                    value
                 } else {
-                    tuple.push(value.cast(column.r#type)?);
-                }
+                    value.cast(*column_ty)?
+                };
+                tuple.push(casted_value);
             }
             data.push(tuple);
         }
diff --git a/sbroad/sbroad-core/src/executor/result/tests.rs b/sbroad/sbroad-core/src/executor/result/tests.rs
index 4ed11d0908..ef79f2e56d 100644
--- a/sbroad/sbroad-core/src/executor/result/tests.rs
+++ b/sbroad/sbroad-core/src/executor/result/tests.rs
@@ -1,8 +1,6 @@
 use pretty_assertions::{assert_eq, assert_ne};
-use tarantool::decimal;
 
 use super::*;
-use crate::ir::relation::Type;
 
 #[test]
 fn box_execute_result_serialize() {
diff --git a/sbroad/sbroad-core/src/executor/tests.rs b/sbroad/sbroad-core/src/executor/tests.rs
index bc83cfb5dd..71d765cbff 100644
--- a/sbroad/sbroad-core/src/executor/tests.rs
+++ b/sbroad/sbroad-core/src/executor/tests.rs
@@ -13,6 +13,16 @@ use crate::ir::value::{LuaValue, Value};
 
 use super::*;
 
+// Helper function to format back sql.
+// The local sql we produce doesn't contain line breaks,
+// but in code it's hard to read such long string, so
+// we insert line breaks and remove them back for
+// string comparison with expected pattern.
+#[cfg(test)]
+pub fn f_sql(s: &str) -> String {
+    s.replace("\n", " ")
+}
+
 #[test]
 fn shard_query() {
     let sql = r#"SELECT "FIRST_NAME" FROM "test_space" where "id" = 1"#;
diff --git a/sbroad/sbroad-core/src/executor/tests/exec_plan.rs b/sbroad/sbroad-core/src/executor/tests/exec_plan.rs
index 6a02039790..50cec9d89a 100644
--- a/sbroad/sbroad-core/src/executor/tests/exec_plan.rs
+++ b/sbroad/sbroad-core/src/executor/tests/exec_plan.rs
@@ -28,15 +28,6 @@ fn reshard_vtable(
     }
 }
 
-// Helper function to format back sql.
-// The local sql we produce doesn't contain line breaks,
-// but in code it's hard to read such long string, so
-// we insert line breaks and remove them back for
-// string comparison with expected pattern.
-fn f_sql(s: &str) -> String {
-    s.replace("\n", " ")
-}
-
 /// Helper function to generate sql from `exec_plan` from given `top_id` node.
 /// Used for testing.
 fn get_sql_from_execution_plan(
@@ -231,17 +222,20 @@ fn exec_plan_subtree_aggregates() {
     } else {
         panic!("Expected MotionPolicy::Segment for local aggregation stage");
     };
+
+    println!("{}", sql.pattern);
     assert_eq!(
         sql,
         PatternWithParams::new(
             f_sql(
                 r#"SELECT "T1"."sys_op" as "column_596",
-("T1"."id") * ("T1"."sys_op") as "column_1632", "T1"."id" as "column_2096",
-count ("T1"."id") as "count_2696", group_concat ("T1"."FIRST_NAME", ?) as "group_concat_2496",
-total ("T1"."id") as "total_2896", min ("T1"."id") as "min_3096", max ("T1"."id") as "max_3296",
-count ("T1"."sysFrom") as "count_1596", sum ("T1"."id") as "sum_1796"
-FROM "test_space" as "T1"
-GROUP BY "T1"."sys_op", ("T1"."id") * ("T1"."sys_op"), "T1"."id""#
+("T1"."id") * ("T1"."sys_op") as "column_1632",
+"T1"."id" as "column_2096", count ("T1"."sysFrom") as "count_1596",
+sum ("T1"."id") as "sum_1796", count ("T1"."id") as "count_2696",
+min ("T1"."id") as "min_3096", group_concat ("T1"."FIRST_NAME", ?) as "group_concat_2496",
+total ("T1"."id") as "total_2896",
+max ("T1"."id") as "max_3296" FROM "test_space" as "T1" GROUP BY "T1"."sys_op",
+("T1"."id") * ("T1"."sys_op"), "T1"."id""#
             ),
             vec![Value::from("o")]
         )
@@ -249,19 +243,18 @@ GROUP BY "T1"."sys_op", ("T1"."id") * ("T1"."sys_op"), "T1"."id""#
 
     // Check main query
     let sql = get_sql_from_execution_plan(exec_plan, top_id, Snapshot::Oldest, TEMPLATE);
+    println!("{}", sql.pattern);
     assert_eq!(
         sql,
         PatternWithParams::new(
-            format!(
-                "{} {} {} {} {} {} {} {}",
-                r#"SELECT ("COL_1") || ("COL_1") as "col_1","#,
-                r#"("COL_1") * (?) + (sum ("COL_9")) as "col_2", sum ("COL_10") as "col_3","#,
-                r#"(sum (DISTINCT "COL_2")) / (count (DISTINCT "COL_3")) as "col_4","#,
-                r#"group_concat ("COL_5", ?) as "col_5","#,
-                r#"sum (CAST ("COL_10" as double)) / sum (CAST ("COL_4" as double)) as "col_6","#,
-                r#"total ("COL_6") as "col_7", min ("COL_7") as "col_8", max ("COL_8") as "col_9""#,
-                r#"FROM (SELECT "COL_1","COL_2","COL_3","COL_4","COL_5","COL_6","COL_7","COL_8","COL_9","COL_10" FROM "TMP_test_0136")"#,
-                r#"GROUP BY "COL_1""#
+            f_sql(
+                r#"SELECT ("COL_1") || ("COL_1") as "col_1",
+("COL_1") * (?) + (sum ("COL_4")) as "col_2",
+sum ("COL_5") as "col_3", (sum (DISTINCT "COL_2")) / (count (DISTINCT "COL_3")) as "col_4",
+group_concat ("COL_8", ?) as "col_5", sum (CAST ("COL_5" as double)) / sum (CAST ("COL_6" as double)) as "col_6",
+total ("COL_9") as "col_7", min ("COL_7") as "col_8",
+max ("COL_10") as "col_9" FROM (SELECT "COL_1","COL_2","COL_3","COL_4","COL_5","COL_6","COL_7","COL_8","COL_9","COL_10" FROM "TMP_test_0136")
+GROUP BY "COL_1""#
             ),
             vec![Value::Unsigned(2), Value::from("o")]
         )
diff --git a/sbroad/sbroad-core/src/executor/vtable.rs b/sbroad/sbroad-core/src/executor/vtable.rs
index 61fc3309cb..50840879ea 100644
--- a/sbroad/sbroad-core/src/executor/vtable.rs
+++ b/sbroad/sbroad-core/src/executor/vtable.rs
@@ -16,7 +16,7 @@ use crate::executor::protocol::{Binary, EncodedRows, EncodedTables};
 use crate::executor::{bucket::Buckets, Vshard};
 use crate::ir::helpers::RepeatableState;
 use crate::ir::node::NodeId;
-use crate::ir::relation::{Column, ColumnRole, Type};
+use crate::ir::relation::{Column, ColumnRole, DerivedType, Type};
 use crate::ir::transformation::redistribution::{ColumnPosition, MotionKey, Target};
 use crate::ir::value::{EncodedValue, LuaValue, MsgPackValue, Value};
 use crate::utils::{write_u32_array_len, ByteCounter};
@@ -100,7 +100,7 @@ pub struct VirtualTable {
 /// (as soon as it's generated automatically).
 #[derive(PartialEq, Debug, Eq, Clone)]
 pub struct VTableColumn {
-    pub r#type: Type,
+    pub r#type: DerivedType,
     pub role: ColumnRole,
     pub is_nullable: bool,
 }
@@ -187,6 +187,15 @@ impl VirtualTable {
         &self.tuples
     }
 
+    /// Retrieve value types of virtual table tuples.
+    #[must_use]
+    pub fn get_types(&self) -> Vec<Vec<DerivedType>> {
+        self.get_tuples()
+            .iter()
+            .map(|tuple| tuple.iter().map(|v| v.get_type()).collect::<Vec<_>>())
+            .collect()
+    }
+
     /// Gets a mutable virtual table tuples list
     #[must_use]
     pub fn get_mut_tuples(&mut self) -> &mut Vec<VTableTuple> {
@@ -194,13 +203,7 @@ impl VirtualTable {
     }
 
     /// Given a vec of [(`is_nullable`, `correct_type`)], fix metadata of each value in the tuples.
-    ///
-    /// # Errors
-    /// - Unable to apply values cast.
-    ///
-    /// # Panics
-    /// - Unacceptable type met.
-    pub fn cast_values(&mut self, fixed_types: &[(bool, Type)]) -> Result<(), SbroadError> {
+    pub fn cast_values(&mut self, fixed_types: &[(bool, DerivedType)]) -> Result<(), SbroadError> {
         for tuple in self.get_mut_tuples() {
             for (i, v) in tuple.iter_mut().enumerate() {
                 let (_, ty) = fixed_types.get(i).expect("Type expected.");
@@ -753,22 +756,18 @@ impl ExecutionPlan {
     }
 }
 
-/// In case Vtable tuples have values of different types, we try to
+/// In case passed types are different, we try to
 /// unify them:
 /// * Some types (like String or Boolean) support only values of the same type. In case we met inconsistency,
 ///   we throw an error.
 /// * Numerical values can be cast according to the following order:
 ///   Decimal > Double > Integer > Unsigned > Null.
 ///
-/// # Errors
-/// - Contains values of inconsistent types.
-///
-/// # Panics
-/// - Internal error.
+/// Each pair in the returned vec is (is_type_nullable, unified_type).
 #[allow(clippy::too_many_lines)]
-pub fn calculate_vtable_unified_types(
-    vtable: &VirtualTable,
-) -> Result<Vec<(bool, Type)>, SbroadError> {
+pub fn calculate_unified_types(
+    types: &Vec<Vec<DerivedType>>,
+) -> Result<Vec<(bool, DerivedType)>, SbroadError> {
     // Map of { type -> types_which_can_be_upcasted_to_given_one }.
     let get_types_less = |ty: &Type| -> &[Type] {
         match ty {
@@ -786,10 +785,10 @@ pub fn calculate_vtable_unified_types(
         }
     };
 
-    let columns_len = vtable.columns.len();
+    let columns_len = types.first().expect("Types vec should not be empty").len();
     let mut nullable_column_indices = HashSet::with_capacity(columns_len);
-    let fix_type = |current_type_unified: &mut Option<Type>, given_type: &Type| {
-        if let Some(current_type_unified) = current_type_unified {
+    let fix_type = |current_type_unified: &mut DerivedType, given_type: &Type| {
+        if let Some(current_type_unified) = current_type_unified.get_mut() {
             if get_types_less(given_type).contains(current_type_unified) {
                 *current_type_unified = *given_type;
             } else if *given_type != *current_type_unified
@@ -797,80 +796,38 @@ pub fn calculate_vtable_unified_types(
             {
                 return Err(SbroadError::Invalid(
                     Entity::Type,
-                    Some(format_smolstr!("Virtual table contains values of inconsistent types: {current_type_unified:?} and {given_type:?}.")),
+                    Some(format_smolstr!("Unable to unify inconsistent types: {current_type_unified:?} and {given_type:?}.")),
                 ));
             }
         } else {
-            *current_type_unified = Some(*given_type);
+            current_type_unified.set(*given_type);
         }
         Ok(())
     };
 
-    let mut unified_types: Vec<Option<Type>> = iter::repeat(None).take(columns_len).collect();
-
-    for tuple in vtable.get_tuples() {
-        for (i, value) in tuple.iter().enumerate() {
-            let current_type_unified = unified_types
-                .get_mut(i)
-                .expect("Unified types vec isn't initialized.");
-            match value {
-                Value::Boolean(_) => {
-                    fix_type(current_type_unified, &Type::Boolean)?;
-                }
-                Value::String(_) => {
-                    fix_type(current_type_unified, &Type::String)?;
-                }
-                Value::Uuid(_) => {
-                    fix_type(current_type_unified, &Type::Uuid)?;
-                }
-                Value::Datetime(_) => {
-                    fix_type(current_type_unified, &Type::Datetime)?;
-                }
-                Value::Null => {
-                    nullable_column_indices.insert(i);
-                }
-                Value::Unsigned(_) => {
-                    fix_type(current_type_unified, &Type::Unsigned)?;
-                }
-                Value::Integer(_) => {
-                    fix_type(current_type_unified, &Type::Integer)?;
-                }
-                Value::Double(_) => {
-                    fix_type(current_type_unified, &Type::Double)?;
-                }
-                Value::Decimal(_) => {
-                    fix_type(current_type_unified, &Type::Decimal)?;
-                }
-                Value::Tuple(_) => panic!("Unexpected tuple under values."),
+    let mut unified_types: Vec<DerivedType> = iter::repeat(DerivedType::unknown())
+        .take(columns_len)
+        .collect();
+
+    for type_tuple in types {
+        for (i, ty) in type_tuple.iter().enumerate() {
+            let current_type_unified = unified_types.get_mut(i).unwrap_or_else(|| {
+                panic!("Unified types vec isn't initialized to retrieve index {i}.")
+            });
+            if let Some(ty) = ty.get() {
+                fix_type(current_type_unified, ty)?;
+            } else {
+                nullable_column_indices.insert(i);
             }
         }
     }
 
-    if unified_types
-        .first()
-        .expect("Unified types vec is empty.")
-        .is_none()
-    {
-        // This is possible in case we deal with VALUES like
-        // `values ((select a from t where false), (select a from t where false), ...)`
-        // where there are no tuples to traverse so that we just infer types returned to us from
-        // local SQL execution.
-        Ok(vtable
-            .get_columns()
-            .iter()
-            .map(|c| (c.is_nullable, c.r#type))
-            .collect())
-    } else {
-        Ok(unified_types
-            .into_iter()
-            .zip(vtable.get_columns().iter())
-            .enumerate()
-            .map(|(i, (t, c))| {
-                let t = if let Some(t) = t { t } else { c.r#type };
-                (nullable_column_indices.contains(&i), t)
-            })
-            .collect())
-    }
+    let res = unified_types
+        .into_iter()
+        .enumerate()
+        .map(|(i, t)| (nullable_column_indices.contains(&i), t))
+        .collect();
+    Ok(res)
 }
 
 #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
diff --git a/sbroad/sbroad-core/src/executor/vtable/tests.rs b/sbroad/sbroad-core/src/executor/vtable/tests.rs
index a104f6f678..e679bafaac 100644
--- a/sbroad/sbroad-core/src/executor/vtable/tests.rs
+++ b/sbroad/sbroad-core/src/executor/vtable/tests.rs
@@ -337,12 +337,13 @@ fn vtable_values_types_casting_single_tuple() {
 
     actual_vtable.add_column(vcolumn_integer_user_non_null());
     actual_vtable.add_tuple(vec![Value::Unsigned(1)]);
-    let unified_types = calculate_vtable_unified_types(&actual_vtable).unwrap();
+    let vtable_types = actual_vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types).unwrap();
     actual_vtable.cast_values(&unified_types).unwrap();
 
     let mut expected_vtable = VirtualTable::new();
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Unsigned,
+        r#type: DerivedType::new(Type::Unsigned),
         role: ColumnRole::User,
         is_nullable: false,
     });
@@ -358,12 +359,13 @@ fn vtable_values_types_casting_two_tuples() {
     actual_vtable.add_column(vcolumn_integer_user_non_null());
     actual_vtable.add_tuple(vec![Value::Unsigned(1)]);
     actual_vtable.add_tuple(vec![Value::Integer(1)]);
-    let unified_types = calculate_vtable_unified_types(&actual_vtable).unwrap();
+    let vtable_types = actual_vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types).unwrap();
     actual_vtable.cast_values(&unified_types).unwrap();
 
     let mut expected_vtable = VirtualTable::new();
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Integer,
+        r#type: DerivedType::new(Type::Integer),
         role: ColumnRole::User,
         is_nullable: false,
     });
@@ -380,12 +382,13 @@ fn vtable_values_types_casting_two_tuples_err() {
     actual_vtable.add_column(vcolumn_integer_user_non_null());
     actual_vtable.add_tuple(vec![Value::Unsigned(1)]);
     actual_vtable.add_tuple(vec![Value::String("name".into())]);
-    let err = calculate_vtable_unified_types(&actual_vtable).unwrap_err();
+    let vtable_types = actual_vtable.get_types();
+    let err = calculate_unified_types(&vtable_types).unwrap_err();
     println!("{}", err);
     assert_eq!(
         true,
         err.to_string()
-            .contains("Virtual table contains values of inconsistent types: Unsigned and String.")
+            .contains("Unable to unify inconsistent types: Unsigned and String.")
     );
 }
 
@@ -396,17 +399,18 @@ fn vtable_values_types_casting_two_columns() {
     actual_vtable.add_column(vcolumn_integer_user_non_null());
     actual_vtable.add_column(vcolumn_integer_user_non_null());
     actual_vtable.add_tuple(vec![Value::Unsigned(1), Value::Integer(1)]);
-    let unified_types = calculate_vtable_unified_types(&actual_vtable).unwrap();
+    let vtable_types = actual_vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types).unwrap();
     actual_vtable.cast_values(&unified_types).unwrap();
 
     let mut expected_vtable = VirtualTable::new();
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Unsigned,
+        r#type: DerivedType::new(Type::Unsigned),
         role: ColumnRole::User,
         is_nullable: false,
     });
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Integer,
+        r#type: DerivedType::new(Type::Integer),
         role: ColumnRole::User,
         is_nullable: false,
     });
@@ -423,17 +427,18 @@ fn vtable_values_types_casting_two_columns_two_tuples() {
     actual_vtable.add_column(vcolumn_integer_user_non_null());
     actual_vtable.add_tuple(vec![Value::Unsigned(1), Value::Integer(1)]);
     actual_vtable.add_tuple(vec![Value::Decimal(Decimal::from(2)), Value::Integer(1)]);
-    let unified_types = calculate_vtable_unified_types(&actual_vtable).unwrap();
+    let vtable_types = actual_vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types).unwrap();
     actual_vtable.cast_values(&unified_types).unwrap();
 
     let mut expected_vtable = VirtualTable::new();
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Decimal,
+        r#type: DerivedType::new(Type::Decimal),
         role: ColumnRole::User,
         is_nullable: false,
     });
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Integer,
+        r#type: DerivedType::new(Type::Integer),
         role: ColumnRole::User,
         is_nullable: false,
     });
@@ -452,17 +457,18 @@ fn vtable_values_types_casting_two_columns_with_nulls() {
     actual_vtable.add_tuple(vec![Value::Unsigned(1), Value::Null]);
     actual_vtable.add_tuple(vec![Value::Null, Value::Null]);
     actual_vtable.add_tuple(vec![Value::Decimal(Decimal::from(2)), Value::Null]);
-    let unified_types = calculate_vtable_unified_types(&actual_vtable).unwrap();
+    let vtable_types = actual_vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types).unwrap();
     actual_vtable.cast_values(&unified_types).unwrap();
 
     let mut expected_vtable = VirtualTable::new();
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Decimal,
+        r#type: DerivedType::new(Type::Decimal),
         role: ColumnRole::User,
         is_nullable: true,
     });
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Integer,
+        r#type: DerivedType::unknown(),
         role: ColumnRole::User,
         is_nullable: true,
     });
@@ -490,17 +496,18 @@ fn vtable_values_types_casting_two_columns_numerical() {
         Value::Decimal(Decimal::from(2)),
         Value::Double(0.5_f64.into()),
     ]);
-    let unified_types = calculate_vtable_unified_types(&actual_vtable).unwrap();
+    let vtable_types = actual_vtable.get_types();
+    let unified_types = calculate_unified_types(&vtable_types).unwrap();
     actual_vtable.cast_values(&unified_types).unwrap();
 
     let mut expected_vtable = VirtualTable::new();
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Decimal,
+        r#type: DerivedType::new(Type::Decimal),
         role: ColumnRole::User,
         is_nullable: true,
     });
     expected_vtable.add_column(VTableColumn {
-        r#type: Type::Decimal,
+        r#type: DerivedType::new(Type::Decimal),
         role: ColumnRole::User,
         is_nullable: true,
     });
diff --git a/sbroad/sbroad-core/src/frontend/sql.rs b/sbroad/sbroad-core/src/frontend/sql.rs
index 5fd2ec9ae1..c477a914dd 100644
--- a/sbroad/sbroad-core/src/frontend/sql.rs
+++ b/sbroad/sbroad-core/src/frontend/sql.rs
@@ -3604,6 +3604,15 @@ impl AbstractSyntaxTree {
 
             let entity = match expr {
                 Node::Expression(expr) => {
+                    if let Expression::Reference(Reference {col_type, ..}) = expr {
+                        if matches!(col_type.get(), Some(Type::Array)) {
+                            return Err(SbroadError::Invalid(
+                                Entity::Expression,
+                                Some(format_smolstr!("Array is not supported as a sort type for ORDER BY"))
+                            ));
+                        }
+                    }
+
                     if let Expression::Constant(Constant {value: Value::Unsigned(index)}) = expr {
                         let index_usize = usize::try_from(*index).map_err(|_| {
                             SbroadError::Invalid(
@@ -3618,11 +3627,13 @@ impl AbstractSyntaxTree {
                         if let Some(alias_node_id) = sq_output.get(index_usize - 1) {
                             let alias_node = plan.get_expression_node(*alias_node_id)?;
                             if let Expression::Alias(Alias { child, .. }) = alias_node {
-                                if let Expression::Reference(Reference { col_type: Type::Array, .. }) = plan.get_expression_node(*child)? {
-                                    return Err(SbroadError::Invalid(
-                                        Entity::Expression,
-                                        Some(format_smolstr!("Array is not supported as a sort type for ORDER BY"))
-                                    ));
+                                if let Expression::Reference(Reference { col_type, .. }) = plan.get_expression_node(*child)? {
+                                    if matches!(col_type.get(), Some(Type::Array)) {
+                                        return Err(SbroadError::Invalid(
+                                            Entity::Expression,
+                                            Some(format_smolstr!("Array is not supported as a sort type for ORDER BY"))
+                                        ));
+                                    }
                                 }
                             }
                         } else {
@@ -3632,11 +3643,6 @@ impl AbstractSyntaxTree {
                             ));
                         }
                         OrderByEntity::Index { value: index_usize }
-                    } else if let Expression::Reference(Reference {col_type: Type::Array, ..}) = expr {
-                        return Err(SbroadError::Invalid(
-                            Entity::Expression,
-                            Some(format_smolstr!("Array is not supported as a sort type for ORDER BY"))
-                        ));
                     } else {
                         // Check that at least one reference is met in expression tree.
                         // Otherwise, ordering expression has no sense.
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs
index 83409f0e45..b9096891e3 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -1725,9 +1725,9 @@ fn front_sql_aggregates() {
 
     let expected_explain = String::from(
         r#"projection ("column_596"::unsigned -> "b", ROW(sum(("count_1496"::unsigned))::unsigned) + ROW(sum(("count_1596"::unsigned))::unsigned) -> "col_1")
-    group by ("column_596"::unsigned) output: ("column_596"::unsigned -> "column_596", "count_1496"::unsigned -> "count_1496", "count_1596"::unsigned -> "count_1596")
+    group by ("column_596"::unsigned) output: ("column_596"::unsigned -> "column_596", "count_1596"::unsigned -> "count_1596", "count_1496"::unsigned -> "count_1496")
         motion [policy: segment([ref("column_596")])]
-            projection ("t"."b"::unsigned -> "column_596", count(("t"."a"::unsigned))::unsigned -> "count_1496", count(("t"."b"::unsigned))::unsigned -> "count_1596")
+            projection ("t"."b"::unsigned -> "column_596", count(("t"."b"::unsigned))::unsigned -> "count_1596", count(("t"."a"::unsigned))::unsigned -> "count_1496")
                 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:
@@ -1736,6 +1736,7 @@ execution options:
 "#,
     );
 
+    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1997,9 +1998,9 @@ fn front_sql_aggregates_with_subexpressions() {
 
     let expected_explain = String::from(
         r#"projection ("column_596"::unsigned -> "b", sum(("count_1496"::unsigned))::unsigned -> "col_1", sum(("count_1796"::unsigned))::unsigned -> "col_2")
-    group by ("column_596"::unsigned) output: ("column_596"::unsigned -> "column_596", "count_1796"::unsigned -> "count_1796", "count_1496"::unsigned -> "count_1496")
+    group by ("column_596"::unsigned) output: ("column_596"::unsigned -> "column_596", "count_1496"::unsigned -> "count_1496", "count_1796"::unsigned -> "count_1796")
         motion [policy: segment([ref("column_596")])]
-            projection ("t"."b"::unsigned -> "column_596", count(("func"(("t"."a"::unsigned))::integer))::unsigned -> "count_1796", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::unsigned -> "count_1496")
+            projection ("t"."b"::unsigned -> "column_596", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned) + ROW(1::unsigned)))::unsigned -> "count_1496", count(("func"(("t"."a"::unsigned))::integer))::unsigned -> "count_1796")
                 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:
@@ -2008,6 +2009,7 @@ execution options:
 "#,
     );
 
+    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2433,7 +2435,7 @@ fn front_sql_insert_single() {
     motion [policy: segment([value(NULL), ref("col_2")])]
         projection (sum(("sum_696"::decimal))::decimal -> "col_1", sum(("count_896"::unsigned))::unsigned -> "col_2")
             motion [policy: full]
-                projection (count(("t"."d"::unsigned))::unsigned -> "count_896", sum(("t"."b"::unsigned))::decimal -> "sum_696")
+                projection (sum(("t"."b"::unsigned))::decimal -> "sum_696", count(("t"."d"::unsigned))::unsigned -> "count_896")
                     scan "t"
 execution options:
     vdbe_max_steps = 45000
@@ -2460,7 +2462,7 @@ fn front_sql_except_single_right() {
     motion [policy: segment([ref("col_1"), ref("col_2")])]
         projection (sum(("sum_1396"::decimal))::decimal -> "col_1", sum(("count_1596"::unsigned))::unsigned -> "col_2")
             motion [policy: full]
-                projection (sum(("t"."a"::unsigned))::decimal -> "sum_1396", count(("t"."b"::unsigned))::unsigned -> "count_1596")
+                projection (count(("t"."b"::unsigned))::unsigned -> "count_1596", sum(("t"."a"::unsigned))::decimal -> "sum_1396")
                     scan "t"
 execution options:
     vdbe_max_steps = 45000
@@ -2485,7 +2487,7 @@ execution options:
     motion [policy: segment([ref("col_2"), ref("col_1")])]
         projection (sum(("sum_1396"::decimal))::decimal -> "col_1", sum(("count_1596"::unsigned))::unsigned -> "col_2")
             motion [policy: full]
-                projection (sum(("t"."a"::unsigned))::decimal -> "sum_1396", count(("t"."b"::unsigned))::unsigned -> "count_1596")
+                projection (count(("t"."b"::unsigned))::unsigned -> "count_1596", sum(("t"."a"::unsigned))::decimal -> "sum_1396")
                     scan "t"
 execution options:
     vdbe_max_steps = 45000
@@ -2510,7 +2512,7 @@ fn front_sql_except_single_left() {
     motion [policy: segment([ref("col_1"), ref("col_2")])]
         projection (sum(("sum_696"::decimal))::decimal -> "col_1", sum(("count_896"::unsigned))::unsigned -> "col_2")
             motion [policy: full]
-                projection (sum(("t"."a"::unsigned))::decimal -> "sum_696", count(("t"."b"::unsigned))::unsigned -> "count_896")
+                projection (count(("t"."b"::unsigned))::unsigned -> "count_896", sum(("t"."a"::unsigned))::decimal -> "sum_696")
                     scan "t"
     projection ("t"."a"::unsigned -> "a", "t"."b"::unsigned -> "b")
         scan "t"
@@ -2537,12 +2539,12 @@ fn front_sql_except_single_both() {
     motion [policy: segment([ref("col_1")])]
         projection (sum(("sum_696"::decimal))::decimal -> "col_1", sum(("count_896"::unsigned))::unsigned -> "col_2")
             motion [policy: full]
-                projection (sum(("t"."a"::unsigned))::decimal -> "sum_696", count(("t"."b"::unsigned))::unsigned -> "count_896")
+                projection (count(("t"."b"::unsigned))::unsigned -> "count_896", sum(("t"."a"::unsigned))::decimal -> "sum_696")
                     scan "t"
     motion [policy: segment([ref("col_1")])]
         projection (sum(("sum_1596"::decimal))::decimal -> "col_1", sum(("sum_1796"::decimal))::decimal -> "col_2")
             motion [policy: full]
-                projection (sum(("t"."a"::unsigned))::decimal -> "sum_1596", sum(("t"."b"::unsigned))::decimal -> "sum_1796")
+                projection (sum(("t"."b"::unsigned))::decimal -> "sum_1796", sum(("t"."a"::unsigned))::decimal -> "sum_1596")
                     scan "t"
 execution options:
     vdbe_max_steps = 45000
@@ -2550,6 +2552,7 @@ execution options:
 "#,
     );
 
+    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2609,9 +2612,9 @@ fn front_sql_groupby_expression3() {
     let plan = sql_to_optimized_ir(input, vec![]);
     let expected_explain = String::from(
         r#"projection ("column_532"::unsigned -> "col_1", "column_832"::unsigned * ROW(sum(("sum_2496"::decimal))::decimal) / ROW(sum(("count_2596"::unsigned))::unsigned) -> "col_2")
-    group by ("column_532"::unsigned, "column_832"::unsigned) output: ("column_532"::unsigned -> "column_532", "column_832"::unsigned -> "column_832", "sum_2496"::decimal -> "sum_2496", "count_2596"::unsigned -> "count_2596")
+    group by ("column_532"::unsigned, "column_832"::unsigned) output: ("column_532"::unsigned -> "column_532", "column_832"::unsigned -> "column_832", "count_2596"::unsigned -> "count_2596", "sum_2496"::decimal -> "sum_2496")
         motion [policy: segment([ref("column_532"), ref("column_832")])]
-            projection (ROW("t"."a"::unsigned) + ROW("t"."b"::unsigned) -> "column_532", (ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)) -> "column_832", sum((ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)))::decimal -> "sum_2496", count((ROW("t"."a"::unsigned) * ROW("t"."b"::unsigned)))::unsigned -> "count_2596")
+            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)))::unsigned -> "count_2596", sum((ROW("t"."c"::unsigned) * ROW("t"."d"::unsigned)))::decimal -> "sum_2496")
                 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:
@@ -2620,8 +2623,8 @@ execution options:
 "#,
     );
 
-    assert_eq!(expected_explain, plan.as_explain().unwrap());
     println!("{}", plan.as_explain().unwrap());
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
 #[test]
@@ -2807,8 +2810,8 @@ execution options:
 "#,
     );
 
-    assert_eq!(expected_explain, plan.as_explain().unwrap());
     println!("{}", plan.as_explain().unwrap());
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
 #[test]
@@ -3058,7 +3061,7 @@ fn front_sql_unique_local_aggregates() {
     let expected_explain = String::from(
         r#"projection (sum(("sum_696"::decimal))::decimal -> "col_1", sum(("count_896"::unsigned))::unsigned -> "col_2", ROW(sum(("sum_696"::decimal))::decimal) + ROW(sum(("count_896"::unsigned))::unsigned) -> "col_3")
     motion [policy: full]
-        projection (count(("t"."a"::unsigned))::unsigned -> "count_896", sum(("t"."a"::unsigned))::decimal -> "sum_696")
+        projection (sum(("t"."a"::unsigned))::decimal -> "sum_696", count(("t"."a"::unsigned))::unsigned -> "count_896")
             scan "t"
 execution options:
     vdbe_max_steps = 45000
@@ -3066,8 +3069,8 @@ execution options:
 "#,
     );
 
-    assert_eq!(expected_explain, plan.as_explain().unwrap());
     println!("{}", plan.as_explain().unwrap());
+    assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
 #[test]
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests/coalesce.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests/coalesce.rs
index adbf994521..5fa0e7ec0e 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests/coalesce.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests/coalesce.rs
@@ -7,7 +7,7 @@ fn coalesce_in_projection() {
     let plan = sql_to_optimized_ir(sql, vec![]);
 
     let expected_explain = String::from(
-        r#"projection (coalesce((NULL::integer, "test_space"."FIRST_NAME"::string))::any -> "col_1")
+        r#"projection (coalesce((NULL::unknown, "test_space"."FIRST_NAME"::string))::any -> "col_1")
     scan "test_space"
 execution options:
     vdbe_max_steps = 45000
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs
index 117a922ae1..a523f489bc 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs
@@ -811,9 +811,9 @@ fn front_sql_global_aggregate5() {
     let expected_explain = String::from(
         r#"projection ("column_1432"::integer -> "col_1", sum(("sum_2896"::decimal))::decimal -> "col_2")
     having ROW(sum(("sum_2296"::decimal::double))::decimal / sum(("count_2296"::decimal::double))::decimal) > ROW(3::unsigned)
-        group by ("column_1432"::integer) output: ("column_1432"::integer -> "column_1432", "sum_2896"::decimal -> "sum_2896", "sum_2296"::decimal -> "sum_2296", "count_2296"::unsigned -> "count_2296")
+        group by ("column_1432"::integer) output: ("column_1432"::integer -> "column_1432", "sum_2296"::decimal -> "sum_2296", "count_2296"::unsigned -> "count_2296", "sum_2896"::decimal -> "sum_2896")
             motion [policy: segment([ref("column_1432")])]
-                projection (ROW("global_t"."b"::integer) + ROW("global_t"."a"::integer) -> "column_1432", sum(("global_t"."a"::integer))::decimal -> "sum_2896", sum(("global_t"."b"::integer))::decimal -> "sum_2296", count(("global_t"."b"::integer))::unsigned -> "count_2296")
+                projection (ROW("global_t"."b"::integer) + ROW("global_t"."a"::integer) -> "column_1432", sum(("global_t"."b"::integer))::decimal -> "sum_2296", count(("global_t"."b"::integer))::unsigned -> "count_2296", sum(("global_t"."a"::integer))::decimal -> "sum_2896")
                     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"
@@ -826,6 +826,8 @@ execution options:
     vtable_max_rows = 5000
 "#,
     );
+
+    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests/params.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests/params.rs
index f395bb7ca4..dba4134759 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests/params.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests/params.rs
@@ -186,7 +186,7 @@ fn front_params2() {
 
     let expected_explain = String::from(
         r#"projection ("test_space"."id"::unsigned -> "id")
-    selection ROW("test_space"."sys_op"::unsigned) = ROW(NULL::integer) and ROW("test_space"."FIRST_NAME"::string) = ROW('hello'::string)
+    selection ROW("test_space"."sys_op"::unsigned) = ROW(NULL::unknown) and ROW("test_space"."FIRST_NAME"::string) = ROW('hello'::string)
         scan "test_space"
 execution options:
     vdbe_max_steps = 45000
@@ -207,7 +207,7 @@ fn front_params3() {
 
     let expected_explain = String::from(
         r#"projection ("test_space"."id"::unsigned -> "id")
-    selection ROW("test_space"."sys_op"::unsigned) = ROW(NULL::integer) and ROW("test_space"."FIRST_NAME"::string) = ROW('кириллица'::string)
+    selection ROW("test_space"."sys_op"::unsigned) = ROW(NULL::unknown) and ROW("test_space"."FIRST_NAME"::string) = ROW('кириллица'::string)
         scan "test_space"
 execution options:
     vdbe_max_steps = 45000
diff --git a/sbroad/sbroad-core/src/ir.rs b/sbroad/sbroad-core/src/ir.rs
index 130d84934e..ff86c5e37b 100644
--- a/sbroad/sbroad-core/src/ir.rs
+++ b/sbroad/sbroad-core/src/ir.rs
@@ -34,7 +34,7 @@ use crate::ir::node::{
     StableFunction, Trim, UnaryExpr, Values,
 };
 use crate::ir::operator::Bool;
-use crate::ir::relation::Column;
+use crate::ir::relation::{Column, DerivedType};
 use crate::ir::tree::traversal::{
     BreadthFirst, PostOrder, PostOrderWithFilter, EXPR_CAPACITY, REL_CAPACITY,
 };
@@ -1824,7 +1824,7 @@ impl Plan {
 }
 
 impl Plan {
-    fn get_param_type(&self, param_id: NodeId) -> Result<Option<Type>, SbroadError> {
+    fn get_param_type(&self, param_id: NodeId) -> Result<DerivedType, SbroadError> {
         let node = self.get_node(param_id)?;
         if let Node::Parameter(ty) = node {
             return Ok(ty.param_type);
@@ -1838,7 +1838,7 @@ 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 MutNode::Parameter(param) = node {
-            param.param_type = Some(ty);
+            param.param_type.set(ty);
             Ok(())
         } else {
             Err(SbroadError::Invalid(
@@ -1876,7 +1876,7 @@ impl Plan {
         let mut inferred_types = vec![None; params_count];
 
         for (node_id, param_idx) in &self.pg_params_map {
-            let param_type = self.get_param_type(*node_id)?;
+            let param_type = *self.get_param_type(*node_id)?.get();
             let inferred_type = inferred_types.get(*param_idx).unwrap_or_else(|| {
                 panic!("param idx {param_idx} exceeds params count {params_count}")
             });
diff --git a/sbroad/sbroad-core/src/ir/aggregates.rs b/sbroad/sbroad-core/src/ir/aggregates.rs
index d7f55f513a..924e5dd0d3 100644
--- a/sbroad/sbroad-core/src/ir/aggregates.rs
+++ b/sbroad/sbroad-core/src/ir/aggregates.rs
@@ -12,6 +12,7 @@ use std::rc::Rc;
 
 use super::expression::{ColumnPositionMap, FunctionFeature, Position};
 use super::node::expression::Expression;
+use super::relation::DerivedType;
 use crate::frontend::sql::ir::SubtreeCloner;
 
 /// The kind of aggregate function
@@ -60,22 +61,22 @@ impl AggregateKind {
     }
 
     #[inline(always)]
-    pub fn to_type(self, plan: &Plan, args: &[NodeId]) -> Result<RelType, SbroadError> {
-        match self {
-            AggregateKind::COUNT => Ok(RelType::Unsigned),
-            AggregateKind::TOTAL => Ok(RelType::Double),
-            AggregateKind::GRCONCAT => Ok(RelType::String),
-            AggregateKind::SUM | AggregateKind::AVG => Ok(RelType::Decimal),
-            AggregateKind::MIN | AggregateKind::MAX => {
-                let child_node =
-                    args.first()
-                        .ok_or(SbroadError::UnexpectedNumberOfValues(format_smolstr!(
-                            "expected at least 1 argument, got 0"
-                        )))?;
-                let expr_node = plan.get_expression_node(*child_node)?;
-                expr_node.calculate_type(plan)
-            }
-        }
+    pub fn to_type(self, plan: &Plan, args: &[NodeId]) -> Result<DerivedType, SbroadError> {
+        let ty =
+            match self {
+                AggregateKind::COUNT => RelType::Unsigned,
+                AggregateKind::TOTAL => RelType::Double,
+                AggregateKind::GRCONCAT => RelType::String,
+                AggregateKind::SUM | AggregateKind::AVG => RelType::Decimal,
+                AggregateKind::MIN | AggregateKind::MAX => {
+                    let child_node = args.first().ok_or(SbroadError::UnexpectedNumberOfValues(
+                        format_smolstr!("expected at least 1 argument, got 0"),
+                    ))?;
+                    let expr_node = plan.get_expression_node(*child_node)?;
+                    return expr_node.calculate_type(plan);
+                }
+            };
+        Ok(DerivedType::new(ty))
     }
 
     #[must_use]
@@ -97,7 +98,11 @@ impl AggregateKind {
     /// - Invalid index
     /// - Node doesn't exist in the plan
     /// - Node is not an expression type
-    pub fn get_arg_type(idx: usize, plan: &Plan, args: &[NodeId]) -> Result<RelType, SbroadError> {
+    pub fn get_arg_type(
+        idx: usize,
+        plan: &Plan,
+        args: &[NodeId],
+    ) -> Result<DerivedType, SbroadError> {
         let arg_id = *args.get(idx).ok_or(SbroadError::NotFound(
             Entity::Index,
             format_smolstr!("no element at index {idx} in args {args:?}"),
@@ -127,23 +132,35 @@ impl AggregateKind {
         match self {
             AggregateKind::SUM | AggregateKind::AVG | AggregateKind::TOTAL => {
                 let arg_type = Self::get_arg_type(0, plan, args)?;
+                let Some(arg_type) = arg_type.get() else {
+                    return Ok(());
+                };
                 if !matches!(
                     arg_type,
                     RelType::Decimal | RelType::Double | RelType::Unsigned | RelType::Integer
                 ) {
-                    err(&arg_type)?;
+                    err(arg_type)?;
                 }
             }
             AggregateKind::MIN | AggregateKind::MAX => {
                 let arg_type = Self::get_arg_type(0, plan, args)?;
+                let Some(arg_type) = arg_type.get() else {
+                    return Ok(());
+                };
                 if !arg_type.is_scalar() {
-                    err(&arg_type)?;
+                    err(arg_type)?;
                 }
             }
             AggregateKind::GRCONCAT => {
-                let first_type = Self::get_arg_type(0, plan, args)?;
+                let arg_type_first = Self::get_arg_type(0, plan, args)?;
+                let Some(first_type) = arg_type_first.get() else {
+                    return Ok(());
+                };
                 if args.len() == 2 {
-                    let second_type = Self::get_arg_type(1, plan, args)?;
+                    let arg_type_second = Self::get_arg_type(1, plan, args)?;
+                    let Some(second_type) = arg_type_second.get() else {
+                        return Ok(());
+                    };
                     if first_type != second_type {
                         return Err(SbroadError::Invalid(
                             Entity::Query,
@@ -155,7 +172,7 @@ impl AggregateKind {
                     }
                 }
                 if !matches!(first_type, RelType::String) {
-                    err(&first_type)?;
+                    err(first_type)?;
                 }
             }
             AggregateKind::COUNT => {}
@@ -317,7 +334,7 @@ impl SimpleAggregate {
         &self,
         parent: NodeId,
         plan: &mut Plan,
-        fun_type: RelType,
+        fun_type: DerivedType,
         mut position_kinds: Vec<PositionKind>,
         is_distinct: bool,
     ) -> Result<NodeId, SbroadError> {
diff --git a/sbroad/sbroad-core/src/ir/api/constant.rs b/sbroad/sbroad-core/src/ir/api/constant.rs
index 4fd7c57462..10f3a5e253 100644
--- a/sbroad/sbroad-core/src/ir/api/constant.rs
+++ b/sbroad/sbroad-core/src/ir/api/constant.rs
@@ -4,6 +4,7 @@ use crate::errors::{Entity, SbroadError};
 use crate::ir::node::expression::Expression;
 use crate::ir::node::{Constant, Node64, NodeId, Parameter};
 use crate::ir::value::Value;
+use crate::ir::DerivedType;
 use crate::ir::{ArenaType, Nodes, Plan};
 
 impl Expression<'_> {
@@ -105,9 +106,12 @@ 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, Node64::Parameter(Parameter { param_type: None }))?;
+            let const_node = self.nodes.replace(
+                const_id,
+                Node64::Parameter(Parameter {
+                    param_type: DerivedType::unknown(),
+                }),
+            )?;
             self.constants.insert(const_id, const_node);
         }
         Ok(())
diff --git a/sbroad/sbroad-core/src/ir/api/parameter.rs b/sbroad/sbroad-core/src/ir/api/parameter.rs
index 440372b2e9..e79e69a175 100644
--- a/sbroad/sbroad-core/src/ir/api/parameter.rs
+++ b/sbroad/sbroad-core/src/ir/api/parameter.rs
@@ -7,12 +7,12 @@ use crate::ir::node::{
     MutNode, Node64, NodeId, Parameter, Procedure, Row, Selection, StableFunction, Trim, UnaryExpr,
     ValuesRow,
 };
+use crate::ir::relation::DerivedType;
 use crate::ir::tree::traversal::{LevelNode, PostOrder, PostOrderWithFilter};
 use crate::ir::value::Value;
 use crate::ir::{ArenaType, Node, OptionParamValue, Plan, ValueIdx};
 use smol_str::format_smolstr;
 
-use crate::ir::relation::Type;
 use ahash::{AHashMap, AHashSet, RandomState};
 use std::collections::HashMap;
 
@@ -402,7 +402,7 @@ impl<'binder> ParamsBinder<'binder> {
     /// Replace parameters in the plan.
     #[allow(clippy::too_many_lines)]
     fn bind_params(&mut self) -> Result<(), SbroadError> {
-        let mut exprs_to_set_ref_type: HashMap<NodeId, Type> = HashMap::new();
+        let mut exprs_to_set_ref_type: HashMap<NodeId, DerivedType> = HashMap::new();
 
         for LevelNode(_, id) in &self.nodes {
             // Before binding, references that referred to
@@ -590,7 +590,12 @@ impl<'binder> ParamsBinder<'binder> {
 
 impl Plan {
     pub fn add_param(&mut self) -> NodeId {
-        self.nodes.push(Parameter { param_type: None }.into())
+        self.nodes.push(
+            Parameter {
+                param_type: DerivedType::unknown(),
+            }
+            .into(),
+        )
     }
 
     /// Bind params related to `Option` clause.
diff --git a/sbroad/sbroad-core/src/ir/ddl.rs b/sbroad/sbroad-core/src/ir/ddl.rs
index b7b78eda7d..39091dcdd1 100644
--- a/sbroad/sbroad-core/src/ir/ddl.rs
+++ b/sbroad/sbroad-core/src/ir/ddl.rs
@@ -20,17 +20,27 @@ impl Default for ColumnDef {
     fn default() -> Self {
         Self {
             name: SmolStr::default(),
-            data_type: RelationType::default(),
+            // TODO: Fix to Option.
+            data_type: RelationType::Any,
             is_nullable: true,
         }
     }
 }
 
-#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
 pub struct ParamDef {
     pub data_type: RelationType,
 }
 
+impl Default for ParamDef {
+    fn default() -> Self {
+        Self {
+            // TODO: Remove later.
+            data_type: RelationType::Any,
+        }
+    }
+}
+
 #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
 pub enum Language {
     #[default]
diff --git a/sbroad/sbroad-core/src/ir/distribution.rs b/sbroad/sbroad-core/src/ir/distribution.rs
index 473a96aa4c..d3091484e4 100644
--- a/sbroad/sbroad-core/src/ir/distribution.rs
+++ b/sbroad/sbroad-core/src/ir/distribution.rs
@@ -60,13 +60,15 @@ impl Key {
                             format_smolstr!("column {name} not found at position {pos}"),
                         )
                     })?;
-                    if !column.r#type.is_scalar() {
-                        return Err(SbroadError::Invalid(
-                            Entity::Column,
-                            Some(format_smolstr!(
-                                "column {name} at position {pos} is not scalar"
-                            )),
-                        ));
+                    if let Some(ty) = column.r#type.get() {
+                        if !ty.is_scalar() {
+                            return Err(SbroadError::Invalid(
+                                Entity::Column,
+                                Some(format_smolstr!(
+                                    "column {name} at position {pos} is not scalar"
+                                )),
+                            ));
+                        }
                     }
                     Ok(pos)
                 }
diff --git a/sbroad/sbroad-core/src/ir/explain.rs b/sbroad/sbroad-core/src/ir/explain.rs
index 07deb80938..0fc4e21a08 100644
--- a/sbroad/sbroad-core/src/ir/explain.rs
+++ b/sbroad/sbroad-core/src/ir/explain.rs
@@ -20,7 +20,6 @@ use crate::ir::node::{
     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,
 };
@@ -31,6 +30,7 @@ use super::node::expression::Expression;
 use super::node::relational::Relational;
 use super::node::Limit;
 use super::operator::{Arithmetic, Bool, Unary};
+use super::relation::DerivedType;
 use super::tree::traversal::{LevelNode, PostOrder, EXPR_CAPACITY, REL_CAPACITY};
 use super::value::Value;
 
@@ -41,7 +41,7 @@ enum ColExpr {
     Arithmetic(Box<ColExpr>, Arithmetic, Box<ColExpr>),
     Bool(Box<ColExpr>, Bool, Box<ColExpr>),
     Unary(Unary, Box<ColExpr>),
-    Column(String, Type),
+    Column(String, DerivedType),
     Cast(Box<ColExpr>, CastType),
     Case(
         Option<Box<ColExpr>>,
@@ -50,7 +50,13 @@ enum ColExpr {
     ),
     Concat(Box<ColExpr>, Box<ColExpr>),
     Like(Box<ColExpr>, Box<ColExpr>, Option<Box<ColExpr>>),
-    StableFunction(SmolStr, Vec<ColExpr>, Option<FunctionFeature>, Type, bool),
+    StableFunction(
+        SmolStr,
+        Vec<ColExpr>,
+        Option<FunctionFeature>,
+        DerivedType,
+        bool,
+    ),
     Trim(Option<TrimKind>, Option<Box<ColExpr>>, Box<ColExpr>),
     Row(Row),
     None,
@@ -99,8 +105,9 @@ impl Display for ColExpr {
                 }
                 let is_distinct = matches!(feature, Some(FunctionFeature::Distinct));
                 let formatted_args = format!("({})", args.iter().format(", "));
+                let func_type_name = func_type.to_string();
                 format!(
-                    "{name}({}{formatted_args})::{func_type}",
+                    "{name}({}{formatted_args})::{func_type_name}",
                     if is_distinct { "distinct " } else { "" }
                 )
             }
diff --git a/sbroad/sbroad-core/src/ir/expression.rs b/sbroad/sbroad-core/src/ir/expression.rs
index e457a672bd..4130242389 100644
--- a/sbroad/sbroad-core/src/ir/expression.rs
+++ b/sbroad/sbroad-core/src/ir/expression.rs
@@ -24,7 +24,7 @@ use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::to_user;
 use crate::ir::node::ReferenceAsteriskSource;
 use crate::ir::operator::Bool;
-use crate::ir::relation::Type;
+use crate::ir::relation::{DerivedType, Type};
 use crate::ir::tree::traversal::{PostOrderWithFilter, EXPR_CAPACITY};
 use crate::ir::{Nodes, Plan, Positions as Targets};
 
@@ -140,7 +140,7 @@ impl Nodes {
         parent: Option<NodeId>,
         targets: Option<Vec<usize>>,
         position: usize,
-        col_type: Type,
+        col_type: DerivedType,
         asterisk_source: Option<ReferenceAsteriskSource>,
     ) -> NodeId {
         let r = Reference {
@@ -1482,7 +1482,8 @@ impl Plan {
                 }
             }
             Expression::Reference(Reference { col_type, .. }) => {
-                return Ok(matches!(col_type, Type::Boolean))
+                let col_type_inner = col_type.get();
+                return Ok(col_type_inner.map_or(true, |t| matches!(t, Type::Boolean)));
             }
             _ => {}
         }
diff --git a/sbroad/sbroad-core/src/ir/expression/cast.rs b/sbroad/sbroad-core/src/ir/expression/cast.rs
index b94bb007e4..bc41b172dc 100644
--- a/sbroad/sbroad-core/src/ir/expression/cast.rs
+++ b/sbroad/sbroad-core/src/ir/expression/cast.rs
@@ -107,7 +107,7 @@ impl Type {
     #[must_use]
     pub fn as_relation_type(&self) -> RelationType {
         match self {
-            Type::Any => RelationType::default(),
+            Type::Any => RelationType::Any,
             Type::Map => RelationType::Map,
             Type::Boolean => RelationType::Boolean,
             Type::Datetime => RelationType::Datetime,
@@ -135,7 +135,7 @@ impl Plan {
 
         let child_plan_node = self.get_mut_node(expr_id)?;
         if let MutNode::Parameter(ty) = child_plan_node {
-            ty.param_type = Some(to_type.as_relation_type());
+            ty.param_type.set(to_type.as_relation_type());
         }
 
         Ok(cast_id)
diff --git a/sbroad/sbroad-core/src/ir/expression/tests.rs b/sbroad/sbroad-core/src/ir/expression/tests.rs
index f8bbd0b795..e9431184e3 100644
--- a/sbroad/sbroad-core/src/ir/expression/tests.rs
+++ b/sbroad/sbroad-core/src/ir/expression/tests.rs
@@ -3,7 +3,7 @@ use crate::ir::tests::{column_integer_user_non_null, sharding_column};
 use pretty_assertions::assert_eq;
 use smol_str::SmolStr;
 
-use crate::ir::relation::{Column, SpaceEngine, Table, Type};
+use crate::ir::relation::{Column, DerivedType, SpaceEngine, Table, Type};
 use crate::ir::value::Value;
 use crate::ir::Plan;
 
@@ -69,7 +69,7 @@ fn derive_expr_type() {
     fn column(name: SmolStr, ty: Type) -> Column {
         Column {
             name,
-            r#type: ty,
+            r#type: DerivedType::new(ty),
             role: Default::default(),
             is_nullable: false,
         }
@@ -106,40 +106,58 @@ fn derive_expr_type() {
         .add_arithmetic_to_plan(b_id, Arithmetic::Divide, c_id)
         .unwrap();
     let expr = plan.get_expression_node(arith_divide_id).unwrap();
-    assert_eq!(expr.calculate_type(&plan).unwrap(), Type::Integer);
+    assert_eq!(
+        expr.calculate_type(&plan).unwrap().get().unwrap(),
+        Type::Integer
+    );
 
     // d*e
     let arith_multiply_id = plan
         .add_arithmetic_to_plan(d_id, Arithmetic::Multiply, e_id)
         .unwrap();
     let expr = plan.get_expression_node(arith_multiply_id).unwrap();
-    assert_eq!(expr.calculate_type(&plan).unwrap(), Type::Decimal);
+    assert_eq!(
+        expr.calculate_type(&plan).unwrap().get().unwrap(),
+        Type::Decimal
+    );
 
     // (b/c + d*e)
     let arith_addition_id = plan
         .add_arithmetic_to_plan(arith_divide_id, Arithmetic::Add, arith_multiply_id)
         .unwrap();
     let expr = plan.get_expression_node(arith_addition_id).unwrap();
-    assert_eq!(expr.calculate_type(&plan).unwrap(), Type::Decimal);
+    assert_eq!(
+        expr.calculate_type(&plan).unwrap().get().unwrap(),
+        Type::Decimal
+    );
 
     // (b/c + d*e) * f
     let arith_multiply_id2 = plan
         .add_arithmetic_to_plan(arith_addition_id, Arithmetic::Multiply, f_id)
         .unwrap();
     let expr = plan.get_expression_node(arith_multiply_id2).unwrap();
-    assert_eq!(expr.calculate_type(&plan).unwrap(), Type::Double);
+    assert_eq!(
+        expr.calculate_type(&plan).unwrap().get().unwrap(),
+        Type::Double
+    );
 
     // a + (b/c + d*e) * f
     let arith_addition_id2 = plan
         .add_arithmetic_to_plan(a_id, Arithmetic::Add, arith_multiply_id2)
         .unwrap();
     let expr = plan.get_expression_node(arith_addition_id2).unwrap();
-    assert_eq!(expr.calculate_type(&plan).unwrap(), Type::Double);
+    assert_eq!(
+        expr.calculate_type(&plan).unwrap().get().unwrap(),
+        Type::Double
+    );
 
     // a + (b/c + d*e) * f - b
     let arith_subract_id = plan
         .add_arithmetic_to_plan(arith_addition_id2, Arithmetic::Subtract, b_id)
         .unwrap();
     let expr = plan.get_expression_node(arith_subract_id).unwrap();
-    assert_eq!(expr.calculate_type(&plan).unwrap(), Type::Double);
+    assert_eq!(
+        expr.calculate_type(&plan).unwrap().get().unwrap(),
+        Type::Double
+    );
 }
diff --git a/sbroad/sbroad-core/src/ir/expression/types.rs b/sbroad/sbroad-core/src/ir/expression/types.rs
index 0ee9a9d180..8f6736c94f 100644
--- a/sbroad/sbroad-core/src/ir/expression/types.rs
+++ b/sbroad/sbroad-core/src/ir/expression/types.rs
@@ -2,7 +2,10 @@ use smol_str::{format_smolstr, ToSmolStr};
 
 use crate::{
     errors::{Entity, SbroadError, TypeError},
-    ir::{relation::Type, Plan},
+    ir::{
+        relation::{DerivedType, Type},
+        Plan,
+    },
 };
 
 use super::{
@@ -11,7 +14,7 @@ use super::{
 };
 
 impl Plan {
-    fn get_node_type(&self, node_id: NodeId) -> Result<Type, SbroadError> {
+    fn get_node_type(&self, node_id: NodeId) -> Result<DerivedType, SbroadError> {
         match self.get_node(node_id)? {
             Node::Expression(expr) => expr.calculate_type(self),
             Node::Relational(relational) => Err(SbroadError::Invalid(
@@ -22,7 +25,7 @@ impl Plan {
             )),
             // Parameter nodes must recalculate their type during
             // binding (see `bind_params` function).
-            Node::Parameter(ty) => Ok(ty.param_type.unwrap_or(Type::default())),
+            Node::Parameter(ty) => Ok(ty.param_type),
             Node::Ddl(ddl) => Err(SbroadError::Invalid(
                 Entity::Node,
                 Some(format_smolstr!("DDL node {ddl:?} has no type")),
@@ -57,93 +60,99 @@ impl Plan {
 
 impl Expression<'_> {
     /// Calculate the type of the expression.
-    ///
-    /// # Errors
-    /// - the row list contains non-expression nodes;
-    ///
-    /// # Panics
-    /// - Plan is in inconsistent state
-    pub fn calculate_type(&self, plan: &Plan) -> Result<Type, SbroadError> {
-        match self {
+    pub fn calculate_type(&self, plan: &Plan) -> Result<DerivedType, SbroadError> {
+        let ty = match self {
             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 {
-                        return if matches!(ret_expr_type, Type::Array)
-                            || matches!(ret_expr_type, Type::Map)
-                        {
-                            Some(Type::Any)
-                        } else {
-                            Some(Type::default())
-                        };
+                // Outer option -- for uninitialized type.
+                // Inner option -- for the case of Null met.
+                let mut case_ty_general: Option<DerivedType> = None;
+                let mut check_types_corresponds = |another_ty: &DerivedType| {
+                    if let Some(case_ty) = case_ty_general {
+                        match (case_ty.get(), another_ty.get()) {
+                            (Some(case_ty), Some(another_ty)) => {
+                                if *case_ty != *another_ty {
+                                    return Err(SbroadError::TypeError(TypeError::TypeMismatch(
+                                        *case_ty,
+                                        *another_ty,
+                                    )));
+                                }
+                            }
+                            (None, Some(_)) => case_ty_general = Some(*another_ty),
+                            (_, _) => {}
+                        }
+                    } else {
+                        case_ty_general = Some(*another_ty)
                     }
-                    None
+                    Ok(())
                 };
 
                 for (_, ret_expr) in when_blocks {
                     let ret_expr_type = plan.get_node_type(*ret_expr)?;
-                    if let Some(case_type) = &case_type {
-                        if let Some(ret_type) = check_types_corresponds(case_type, &ret_expr_type) {
-                            return Ok(ret_type);
-                        }
-                    } else {
-                        case_type = Some(ret_expr_type);
-                    }
+                    check_types_corresponds(&ret_expr_type)?
                 }
-                let case_type_unwrapped = case_type.expect("Case WHEN type must be known");
                 if let Some(else_expr) = else_expr {
                     let else_expr_type = plan.get_node_type(*else_expr)?;
-                    if let Some(ret_type) =
-                        check_types_corresponds(&case_type_unwrapped, &else_expr_type)
-                    {
-                        return Ok(ret_type);
-                    }
+                    check_types_corresponds(&else_expr_type)?
                 }
-                Ok(case_type_unwrapped)
+                case_ty_general.expect("Case type must be known")
             }
             Expression::Alias(Alias { child, .. })
             | Expression::ExprInParentheses(ExprInParentheses { child }) => {
-                plan.get_node_type(*child)
+                plan.get_node_type(*child)?
             }
             Expression::Bool(_) | Expression::Unary(_) | Expression::Like { .. } => {
-                Ok(Type::Boolean)
+                DerivedType::new(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) {
+
+                let (left_type, right_type) = match (left_type.get(), right_type.get()) {
+                    (Some(l_t), Some(r_t)) => (l_t, r_t),
+                    _ => {
+                        return Err(SbroadError::Invalid(
+                            Entity::Expression,
+                            Some(format_smolstr!(
+                                "Null type is not supported for arithmetic expression"
+                            )),
+                        ))
+                    }
+                };
+
+                let res = match (left_type, right_type) {
                     (Type::Double, Type::Double | Type::Unsigned | Type::Integer | Type::Decimal)
                     | (Type::Unsigned | Type::Integer | Type::Decimal, Type::Double) => {
-                        Ok(Type::Double)
+                        Type::Double
                     }
                     (Type::Decimal, Type::Decimal | Type::Unsigned | Type::Integer)
-                    | (Type::Unsigned | Type::Integer, Type::Decimal) => Ok(Type::Decimal),
+                    | (Type::Unsigned | Type::Integer, Type::Decimal) => Type::Decimal,
                     (Type::Integer, Type::Unsigned | Type::Integer)
-                    | (Type::Unsigned, Type::Integer) => Ok(Type::Integer),
-                    (Type::Unsigned, Type::Unsigned) => Ok(Type::Unsigned),
-                    _ => Err(SbroadError::Invalid(
+                    | (Type::Unsigned, Type::Integer) => Type::Integer,
+                    (Type::Unsigned, Type::Unsigned) => Type::Unsigned,
+                    _ => return Err(SbroadError::Invalid(
                         Entity::Expression,
                         Some(format_smolstr!("types {left_type} and {right_type} are not supported for arithmetic expression ({:?} {op:?} {:?})",
                         plan.get_node(*left)?, plan.get_node(*right)?)),
                     )),
-                }
+                };
+                DerivedType::new(res)
             }
-            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),
+            Expression::Cast(Cast { to, .. }) => DerivedType::new(to.as_relation_type()),
+            Expression::Trim(_) | Expression::Concat(_) => DerivedType::new(Type::String),
+            Expression::Constant(Constant { value, .. }) => value.get_type(),
+            Expression::Reference(Reference { col_type, .. }) => *col_type,
             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)
+                    expr.calculate_type(plan)?
                 } else {
-                    Ok(Type::Array)
+                    DerivedType::new(Type::Array)
                 }
             }
             Expression::StableFunction(StableFunction {
@@ -160,27 +169,31 @@ impl Expression<'_> {
                             .first()
                             .expect("min/max functions must have an argument");
                         let expr = plan.get_expression_node(*expr_id)?;
-                        expr.calculate_type(plan)
+                        expr.calculate_type(plan)?
                     }
                     "coalesce" => {
-                        let first_id = children.first().expect("coalesce must have children");
-                        let child = plan.get_expression_node(*first_id)?;
-                        let ty = child.calculate_type(plan)?;
-                        // ensure all the types are the same
+                        let mut last_ty = DerivedType::unknown();
                         for child_id in children {
                             let child = plan.get_expression_node(*child_id)?;
-                            let child_ty = child.calculate_type(plan)?;
-                            if child_ty != ty {
-                                return Err(TypeError::TypeMismatch(ty, child_ty).into());
+                            let ty = child.calculate_type(plan)?;
+                            if let Some(ty) = ty.get() {
+                                if let Some(last_ty) = last_ty.get() {
+                                    if ty != last_ty {
+                                        return Err(TypeError::TypeMismatch(*last_ty, *ty).into());
+                                    }
+                                } else {
+                                    last_ty.set(*ty)
+                                }
                             }
                         }
-                        Ok(ty)
+                        last_ty
                     }
-                    _ => Ok(*func_type),
+                    _ => *func_type,
                 }
             }
-            Expression::CountAsterisk(_) => Ok(Type::Integer),
-        }
+            Expression::CountAsterisk(_) => DerivedType::new(Type::Integer),
+        };
+        Ok(ty)
     }
 
     /// Returns the recalculated type of the expression.
@@ -199,7 +212,7 @@ impl Expression<'_> {
     ///
     /// # Errors
     /// - if the reference is invalid;
-    pub fn recalculate_type(&self, plan: &Plan) -> Result<Type, SbroadError> {
+    pub fn recalculate_type(&self, plan: &Plan) -> Result<DerivedType, SbroadError> {
         if let Expression::Reference(Reference {
             parent,
             targets,
@@ -246,7 +259,7 @@ impl Expression<'_> {
 }
 
 impl MutExpression<'_> {
-    pub fn set_ref_type(&mut self, new_type: Type) {
+    pub fn set_ref_type(&mut self, new_type: DerivedType) {
         if let MutExpression::Reference(Reference { col_type, .. }) = self {
             *col_type = new_type;
         }
diff --git a/sbroad/sbroad-core/src/ir/function.rs b/sbroad/sbroad-core/src/ir/function.rs
index f0d433b46e..79509a861d 100644
--- a/sbroad/sbroad-core/src/ir/function.rs
+++ b/sbroad/sbroad-core/src/ir/function.rs
@@ -2,12 +2,12 @@ use crate::errors::{Entity, SbroadError};
 use crate::executor::engine::helpers::to_user;
 use crate::ir::aggregates::AggregateKind;
 use crate::ir::node::{NodeId, StableFunction};
-use crate::ir::relation::Type;
 use crate::ir::Plan;
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
 
 use super::expression::FunctionFeature;
+use super::relation::DerivedType;
 
 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
 pub enum Behavior {
@@ -23,7 +23,7 @@ pub enum Behavior {
 pub struct Function {
     pub name: SmolStr,
     pub behavior: Behavior,
-    pub func_type: Type,
+    pub func_type: DerivedType,
     /// True if this function is provided by tarantool,
     /// when referencing this func in local sql, we must
     /// not use quotes
@@ -32,7 +32,7 @@ pub struct Function {
 
 impl Function {
     #[must_use]
-    pub fn new(name: SmolStr, behavior: Behavior, func_type: Type, is_system: bool) -> Self {
+    pub fn new(name: SmolStr, behavior: Behavior, func_type: DerivedType, is_system: bool) -> Self {
         Self {
             name,
             behavior,
@@ -42,7 +42,7 @@ impl Function {
     }
 
     #[must_use]
-    pub fn new_stable(name: SmolStr, func_type: Type, is_system: bool) -> Self {
+    pub fn new_stable(name: SmolStr, func_type: DerivedType, is_system: bool) -> Self {
         Self::new(name, Behavior::Stable, func_type, is_system)
     }
 
diff --git a/sbroad/sbroad-core/src/ir/helpers.rs b/sbroad/sbroad-core/src/ir/helpers.rs
index ff0aa44a97..90e1ac92aa 100644
--- a/sbroad/sbroad-core/src/ir/helpers.rs
+++ b/sbroad/sbroad-core/src/ir/helpers.rs
@@ -186,10 +186,11 @@ impl Plan {
                         writeln!(buf, "NO TARGETS")?;
                     }
 
+                    let col_type_str = col_type.to_string();
                     writeln_with_tabulation(
                         buf,
                         tabulation_number + 1,
-                        format!("Column type: {col_type}").as_str(),
+                        format!("Column type: {col_type_str}").as_str(),
                     )?;
                 }
                 Expression::Row(Row { list, distribution }) => {
diff --git a/sbroad/sbroad-core/src/ir/helpers/tests.rs b/sbroad/sbroad-core/src/ir/helpers/tests.rs
index f13bf7a13e..daa4424840 100644
--- a/sbroad/sbroad-core/src/ir/helpers/tests.rs
+++ b/sbroad/sbroad-core/src/ir/helpers/tests.rs
@@ -20,11 +20,11 @@ fn simple_select() {
 	Output_id: 064
 		[id: 064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 032] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer, asterisk_source: None })]
-				[id: 132] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 1, col_type: String, asterisk_source: None })]
-				[id: 232] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean, asterisk_source: None })]
-				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned, asterisk_source: None })]
-				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned, asterisk_source: None })]
+				[id: 032] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
+				[id: 132] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
+				[id: 232] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 2, col_type: DerivedType(Some(Boolean)), asterisk_source: None })]
+				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 3, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 4, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 364] relation: Projection
@@ -33,7 +33,7 @@ fn simple_select() {
 	Output_id: 264
 		[id: 264] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 532] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String, asterisk_source: None })]
+				[id: 532] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
 ---------------------------------------------
 "#);
 
@@ -59,11 +59,11 @@ fn simple_join() {
 	Output_id: 064
 		[id: 064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 032] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 0, col_type: Unsigned, asterisk_source: None })]
-				[id: 132] expression: Alias [name = sysFrom, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 1, col_type: Unsigned, asterisk_source: None })]
-				[id: 232] expression: Alias [name = FIRST_NAME, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 2, col_type: String, asterisk_source: None })]
-				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned, asterisk_source: None })]
-				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned, asterisk_source: None })]
+				[id: 032] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 0, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 132] expression: Alias [name = sysFrom, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 1, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 232] expression: Alias [name = FIRST_NAME, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 2, col_type: DerivedType(Some(String)), asterisk_source: None })]
+				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 3, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 4, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 364] relation: Projection
@@ -72,7 +72,7 @@ fn simple_join() {
 	Output_id: 264
 		[id: 264] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 532] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned, asterisk_source: None })]
+				[id: 532] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 564] relation: ScanSubQuery
@@ -82,7 +82,7 @@ fn simple_join() {
 	Output_id: 464
 		[id: 464] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 632] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 5, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned, asterisk_source: None })]
+				[id: 632] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 5, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 764] relation: ScanRelation
@@ -91,11 +91,11 @@ fn simple_join() {
 	Output_id: 664
 		[id: 664] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 732] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer, asterisk_source: None })]
-				[id: 832] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 1, col_type: String, asterisk_source: None })]
-				[id: 932] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean, asterisk_source: None })]
-				[id: 1032] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned, asterisk_source: None })]
-				[id: 1132] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned, asterisk_source: None })]
+				[id: 732] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
+				[id: 832] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
+				[id: 932] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 2, col_type: DerivedType(Some(Boolean)), asterisk_source: None })]
+				[id: 1032] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 3, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 1132] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 4, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 964] relation: Projection
@@ -104,7 +104,7 @@ fn simple_join() {
 	Output_id: 864
 		[id: 864] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 1232] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer, asterisk_source: None })]
+				[id: 1232] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 1164] relation: ScanSubQuery
@@ -114,7 +114,7 @@ fn simple_join() {
 	Output_id: 1064
 		[id: 1064] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 1332] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 11, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer, asterisk_source: None })]
+				[id: 1332] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 11, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 0136] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] }), alias = t2]
@@ -123,7 +123,7 @@ fn simple_join() {
 	Output_id: 2064
 		[id: 2064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[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, asterisk_source: None })]
+				[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: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 1564] relation: InnerJoin
@@ -153,8 +153,8 @@ fn simple_join() {
 	Output_id: 1464
 		[id: 1464] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [1] }, Key { positions: [0] }}) })]
 			List:
-				[id: 1532] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 15, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned, asterisk_source: None })]
-				[id: 1632] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 15, arena_type: Arena64 }), targets: Some([1]), position: 0, col_type: Integer, asterisk_source: None })]
+				[id: 1532] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 15, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 1632] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 15, arena_type: Arena64 }), targets: Some([1]), position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 1764] relation: Projection
@@ -163,7 +163,7 @@ fn simple_join() {
 	Output_id: 1664
 		[id: 1664] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 1732] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 17, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Unsigned, asterisk_source: None })]
+				[id: 1732] expression: Alias [name = id, child = Reference(Reference { parent: Some(NodeId { offset: 17, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 "#);
 
@@ -197,11 +197,11 @@ fn simple_join_subtree() {
 	Output_id: 664
 		[id: 664] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 732] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer, asterisk_source: None })]
-				[id: 832] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 1, col_type: String, asterisk_source: None })]
-				[id: 932] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean, asterisk_source: None })]
-				[id: 1032] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned, asterisk_source: None })]
-				[id: 1132] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned, asterisk_source: None })]
+				[id: 732] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
+				[id: 832] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
+				[id: 932] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 2, col_type: DerivedType(Some(Boolean)), asterisk_source: None })]
+				[id: 1032] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 3, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 1132] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 7, arena_type: Arena64 }), targets: None, position: 4, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 964] relation: Projection
@@ -210,7 +210,7 @@ fn simple_join_subtree() {
 	Output_id: 864
 		[id: 864] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 1232] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer, asterisk_source: None })]
+				[id: 1232] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 1164] relation: ScanSubQuery
@@ -220,7 +220,7 @@ fn simple_join_subtree() {
 	Output_id: 1064
 		[id: 1064] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 1332] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 11, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer, asterisk_source: None })]
+				[id: 1332] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 11, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 0136] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] }), alias = t2]
@@ -229,7 +229,7 @@ fn simple_join_subtree() {
 	Output_id: 2064
 		[id: 2064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[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, asterisk_source: None })]
+				[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: DerivedType(Some(Integer)), asterisk_source: None })]
 ---------------------------------------------
 "#
     );
@@ -252,26 +252,26 @@ fn simple_aggregation_with_group_by() {
 	Output_id: 064
 		[id: 064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 032] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 0, col_type: Integer, asterisk_source: None })]
-				[id: 132] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 1, col_type: String, asterisk_source: None })]
-				[id: 232] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 2, col_type: Boolean, asterisk_source: None })]
-				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 3, col_type: Unsigned, asterisk_source: None })]
-				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 4, col_type: Unsigned, asterisk_source: None })]
+				[id: 032] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
+				[id: 132] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
+				[id: 232] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 2, col_type: DerivedType(Some(Boolean)), asterisk_source: None })]
+				[id: 332] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 3, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 432] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 1, arena_type: Arena64 }), targets: None, position: 4, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 364] relation: GroupBy [is_final = false]
 	Gr_cols:
-		Gr_col: Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String, asterisk_source: None })
+		Gr_col: Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })
 	Children:
 		Child_id = 164
 	Output_id: 264
 		[id: 264] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0, 1] }}) })]
 			List:
-				[id: 532] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: Integer, asterisk_source: None })]
-				[id: 632] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String, asterisk_source: None })]
-				[id: 732] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 2, col_type: Boolean, asterisk_source: None })]
-				[id: 832] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 3, col_type: Unsigned, asterisk_source: None })]
-				[id: 932] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 4, col_type: Unsigned, asterisk_source: None })]
+				[id: 532] expression: Alias [name = identification_number, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(Integer)), asterisk_source: None })]
+				[id: 632] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
+				[id: 732] expression: Alias [name = product_units, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 2, col_type: DerivedType(Some(Boolean)), asterisk_source: None })]
+				[id: 832] expression: Alias [name = sys_op, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 3, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
+				[id: 932] expression: Alias [name = bucket_id, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 4, col_type: DerivedType(Some(Unsigned)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 764] relation: Projection
@@ -280,7 +280,7 @@ fn simple_aggregation_with_group_by() {
 	Output_id: 664
 		[id: 664] expression: Row [distribution = Some(Any)]
 			List:
-				[id: 1132] expression: Alias [name = column_596, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: String, asterisk_source: None })]
+				[id: 1132] expression: Alias [name = column_596, child = Reference(Reference { parent: Some(NodeId { offset: 3, arena_type: Arena64 }), targets: Some([0]), position: 1, col_type: DerivedType(Some(String)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 0136] relation: Motion [policy = Segment(MotionKey { targets: [Reference(0)] }), alias = None]
@@ -289,18 +289,18 @@ fn simple_aggregation_with_group_by() {
 	Output_id: 1064
 		[id: 1064] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 1332] expression: Alias [name = column_596, child = Reference(Reference { parent: Some(NodeId { offset: 0, arena_type: Arena136 }), targets: Some([0]), position: 0, col_type: String, asterisk_source: None })]
+				[id: 1332] expression: Alias [name = column_596, child = Reference(Reference { parent: Some(NodeId { offset: 0, arena_type: Arena136 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(String)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 964] relation: GroupBy [is_final = true]
 	Gr_cols:
-		Gr_col: Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String, asterisk_source: None })
+		Gr_col: Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(String)), asterisk_source: None })
 	Children:
 		Child_id = 0136
 	Output_id: 864
 		[id: 864] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 1232] expression: Alias [name = column_596, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String, asterisk_source: None })]
+				[id: 1232] expression: Alias [name = column_596, child = Reference(Reference { parent: Some(NodeId { offset: 9, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(String)), asterisk_source: None })]
 ---------------------------------------------
 ---------------------------------------------
 [id: 564] relation: Projection
@@ -309,7 +309,7 @@ fn simple_aggregation_with_group_by() {
 	Output_id: 464
 		[id: 464] expression: Row [distribution = Some(Segment { keys: KeySet({Key { positions: [0] }}) })]
 			List:
-				[id: 1032] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 5, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: String, asterisk_source: None })]
+				[id: 1032] expression: Alias [name = product_code, child = Reference(Reference { parent: Some(NodeId { offset: 5, arena_type: Arena64 }), targets: Some([0]), position: 0, col_type: DerivedType(Some(String)), asterisk_source: None })]
 ---------------------------------------------
 "#);
 
diff --git a/sbroad/sbroad-core/src/ir/node.rs b/sbroad/sbroad-core/src/ir/node.rs
index a228fae44c..f0b4b5003d 100644
--- a/sbroad/sbroad-core/src/ir/node.rs
+++ b/sbroad/sbroad-core/src/ir/node.rs
@@ -19,13 +19,13 @@ use super::{
     ddl::AlterSystemType,
     expression::{cast, FunctionFeature, TrimKind},
     operator::{self, ConflictStrategy, JoinKind, OrderByElement, UpdateStrategy},
+    relation::DerivedType,
 };
 use crate::ir::{
     acl::{AlterOption, GrantRevokeType},
     ddl::{ColumnDef, Language, ParamDef, SetParamScopeType, SetParamValue},
     distribution::Distribution,
     helpers::RepeatableState,
-    relation::Type,
     transformation::redistribution::{ColumnPosition, MotionPolicy, Program},
     value::Value,
 };
@@ -257,7 +257,7 @@ pub struct Reference {
     /// Expression position in the input tuple (i.e. `Alias` column).
     pub position: usize,
     /// Referred column type in the input tuple.
-    pub col_type: Type,
+    pub col_type: DerivedType,
     /// Field indicating whether this reference resulted
     /// from an asterisk "*" under projection.
     pub asterisk_source: Option<ReferenceAsteriskSource>,
@@ -308,7 +308,7 @@ pub struct StableFunction {
     /// Optional function feature.
     pub feature: Option<FunctionFeature>,
     /// Function return type.
-    pub func_type: Type,
+    pub func_type: DerivedType,
     /// Whether function is provided by tarantool,
     /// when referencing these funcs from local
     /// sql we must not use quotes.
@@ -380,7 +380,7 @@ impl From<Case> for NodeAligned {
 
 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
 pub struct Parameter {
-    pub param_type: Option<Type>,
+    pub param_type: DerivedType,
 }
 
 impl From<Parameter> for NodeAligned {
diff --git a/sbroad/sbroad-core/src/ir/operator.rs b/sbroad/sbroad-core/src/ir/operator.rs
index 263e6e7fd9..7c29601338 100644
--- a/sbroad/sbroad-core/src/ir/operator.rs
+++ b/sbroad/sbroad-core/src/ir/operator.rs
@@ -3,6 +3,7 @@
 //! Contains operator nodes that transform the tuples in IR tree.
 
 use crate::executor::engine::helpers::to_user;
+use crate::executor::vtable::calculate_unified_types;
 use crate::frontend::sql::get_unnamed_column_alias;
 use crate::ir::api::children::Children;
 use crate::ir::expression::PlanExpr;
@@ -28,6 +29,7 @@ use super::expression::{ColumnPositionMap, ExpressionId};
 use super::node::expression::{Expression, MutExpression};
 use super::node::relational::{MutRelational, Relational};
 use super::node::{ArenaType, Limit, Node, NodeAligned, SelectWithoutScan};
+use super::relation::DerivedType;
 use super::transformation::redistribution::{MotionPolicy, Program};
 use super::tree::traversal::{LevelNode, PostOrderWithFilter, EXPR_CAPACITY};
 use crate::ir::distribution::{Distribution, Key, KeySet};
@@ -1452,22 +1454,32 @@ impl Plan {
             ));
         };
 
+        let mut types = Vec::new();
+        for row_id in &value_rows {
+            let value_row = self.get_relation_node(*row_id)?;
+            let output_id = value_row.output();
+            let output: Expression<'_> = self.get_expression_node(output_id)?;
+            let row_list = output.get_row_list()?;
+            let tuple_types: Result<Vec<DerivedType>, SbroadError> = row_list
+                .iter()
+                .map(|col_id| {
+                    let col_expr = self.get_expression_node(*col_id)?;
+                    col_expr.calculate_type(self)
+                })
+                .collect();
+            types.push(tuple_types?)
+        }
+        let unified_types = calculate_unified_types(&types)?;
+
         // Generate a row of aliases referencing all the children.
         let mut aliases: Vec<NodeId> = Vec::with_capacity(names.len());
-        let columns = last_output.clone_row_list()?;
         for (pos, name) in names.iter().enumerate() {
-            let col_id = *columns.get(pos).ok_or_else(|| {
-                SbroadError::UnexpectedNumberOfValues(format_smolstr!(
-                    "Values node has no column at position {pos}"
-                ))
-            })?;
-            let col_expr = self.get_expression_node(col_id)?;
-            let col_type = col_expr.calculate_type(self)?;
+            let unified_type = unified_types[pos].1;
             let ref_id = self.nodes.add_ref(
                 None,
                 Some((0..value_rows.len()).collect::<Vec<usize>>()),
                 pos,
-                col_type,
+                unified_type,
                 None,
             );
             let alias_id = self.nodes.add_alias(name, ref_id)?;
diff --git a/sbroad/sbroad-core/src/ir/operator/tests.rs b/sbroad/sbroad-core/src/ir/operator/tests.rs
index 48bade8fab..5fc2bbd6fb 100644
--- a/sbroad/sbroad-core/src/ir/operator/tests.rs
+++ b/sbroad/sbroad-core/src/ir/operator/tests.rs
@@ -230,7 +230,12 @@ fn insert() {
         vec![
             column_user_non_null(SmolStr::from("a"), Type::Unsigned),
             column_user_non_null(SmolStr::from("b"), Type::Unsigned),
-            Column::new("c", Type::Unsigned, ColumnRole::Sharding, true),
+            Column::new(
+                "c",
+                DerivedType::new(Type::Unsigned),
+                ColumnRole::Sharding,
+                true,
+            ),
         ],
         &["a"],
         &["a"],
diff --git a/sbroad/sbroad-core/src/ir/relation.rs b/sbroad/sbroad-core/src/ir/relation.rs
index 558a8e948f..576cab1581 100644
--- a/sbroad/sbroad-core/src/ir/relation.rs
+++ b/sbroad/sbroad-core/src/ir/relation.rs
@@ -29,7 +29,7 @@ const DEFAULT_VALUE: Value = Value::Null;
 
 /// Supported column types, which is used in a schema only.
 /// This `Type` is derived from the result's metadata.
-#[derive(Serialize, Default, Deserialize, PartialEq, Hash, Debug, Eq, Clone, Copy)]
+#[derive(Serialize, Deserialize, PartialEq, Hash, Debug, Eq, Clone, Copy)]
 pub enum Type {
     Any,
     Map,
@@ -38,13 +38,39 @@ pub enum Type {
     Datetime,
     Decimal,
     Double,
-    #[default]
     Integer,
     String,
     Uuid,
     Unsigned,
 }
 
+/// Derived type (`Some<Type>`) or its absence (`None`).
+/// Type absence is possible in case we met a Null.
+#[derive(Serialize, Deserialize, PartialEq, Hash, Debug, Eq, Clone, Copy)]
+pub struct DerivedType(Option<Type>);
+
+impl DerivedType {
+    pub fn unknown() -> Self {
+        Self(None)
+    }
+
+    pub fn new(ty: Type) -> Self {
+        Self(Some(ty))
+    }
+
+    pub fn get(&self) -> &Option<Type> {
+        &self.0
+    }
+
+    pub fn get_mut(&mut self) -> &mut Option<Type> {
+        &mut self.0
+    }
+
+    pub fn set(&mut self, ty: Type) {
+        self.0 = Some(ty)
+    }
+}
+
 impl fmt::Display for Type {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         match self {
@@ -63,6 +89,15 @@ impl fmt::Display for Type {
     }
 }
 
+impl fmt::Display for DerivedType {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self.0 {
+            None => write!(f, "unknown"),
+            Some(t) => t.fmt(f),
+        }
+    }
+}
+
 impl From<&Type> for FieldType {
     fn from(data_type: &Type) -> Self {
         match data_type {
@@ -204,7 +239,7 @@ pub struct Column {
     /// Column name.
     pub name: SmolStr,
     /// Column type.
-    pub r#type: Type,
+    pub r#type: DerivedType,
     /// Column role.
     pub role: ColumnRole,
     /// Column is_nullable status.
@@ -216,7 +251,7 @@ impl Default for Column {
     fn default() -> Self {
         Column {
             name: SmolStr::default(),
-            r#type: Type::default(),
+            r#type: DerivedType::unknown(),
             role: ColumnRole::default(),
             is_nullable: true,
         }
@@ -225,18 +260,22 @@ impl Default for Column {
 
 impl From<Column> for Field {
     fn from(column: Column) -> Self {
-        let field = match column.r#type {
-            Type::Boolean => Field::boolean(column.name),
-            Type::Datetime => Field::datetime(column.name),
-            Type::Decimal => Field::decimal(column.name),
-            Type::Double => Field::double(column.name),
-            Type::Integer => Field::integer(column.name),
-            Type::String => Field::string(column.name),
-            Type::Uuid => Field::uuid(column.name),
-            Type::Unsigned => Field::unsigned(column.name),
-            Type::Array => Field::array(column.name),
-            Type::Any => Field::any(column.name),
-            Type::Map => Field::map(column.name),
+        let field = if let Some(ty) = column.r#type.get() {
+            match ty {
+                Type::Boolean => Field::boolean(column.name),
+                Type::Datetime => Field::datetime(column.name),
+                Type::Decimal => Field::decimal(column.name),
+                Type::Double => Field::double(column.name),
+                Type::Integer => Field::integer(column.name),
+                Type::String => Field::string(column.name),
+                Type::Uuid => Field::uuid(column.name),
+                Type::Unsigned => Field::unsigned(column.name),
+                Type::Array => Field::array(column.name),
+                Type::Any => Field::any(column.name),
+                Type::Map => Field::map(column.name),
+            }
+        } else {
+            Field::scalar(column.name)
         };
         field.is_nullable(true)
     }
@@ -244,7 +283,11 @@ impl From<Column> for Field {
 
 impl From<&Column> for FieldType {
     fn from(column: &Column) -> Self {
-        FieldType::from(&column.r#type)
+        if let Some(ty) = &column.r#type.get() {
+            FieldType::from(ty)
+        } else {
+            FieldType::Scalar
+        }
     }
 }
 
@@ -263,19 +306,25 @@ impl SerSerialize for Column {
     {
         let mut map = serializer.serialize_map(Some(3))?;
         map.serialize_entry("name", &self.name)?;
-        match &self.r#type {
-            Type::Boolean => map.serialize_entry("type", "boolean")?,
-            Type::Datetime => map.serialize_entry("type", "datetime")?,
-            Type::Decimal => map.serialize_entry("type", "decimal")?,
-            Type::Double => map.serialize_entry("type", "double")?,
-            Type::Integer => map.serialize_entry("type", "integer")?,
-            Type::String => map.serialize_entry("type", "string")?,
-            Type::Uuid => map.serialize_entry("type", "uuid")?,
-            Type::Unsigned => map.serialize_entry("type", "unsigned")?,
-            Type::Array => map.serialize_entry("type", "array")?,
-            Type::Any => map.serialize_entry("type", "any")?,
-            Type::Map => map.serialize_entry("type", "map")?,
-        }
+
+        let type_str = match &self.r#type.get() {
+            Some(ty) => match ty {
+                Type::Boolean => "boolean",
+                Type::Datetime => "datetime",
+                Type::Decimal => "decimal",
+                Type::Double => "double",
+                Type::Integer => "integer",
+                Type::String => "string",
+                Type::Uuid => "uuid",
+                Type::Unsigned => "unsigned",
+                Type::Array => "array",
+                Type::Any => "any",
+                Type::Map => "map",
+            },
+            None => "unknown",
+        };
+        map.serialize_entry("type", type_str)?;
+
         map.serialize_entry(
             "role",
             match self.role {
@@ -322,28 +371,22 @@ impl<'de> Visitor<'de> for ColumnVisitor {
 
         let is_nullable = matches!(column_is_nullable.as_str(), "true");
 
-        match column_type.as_str() {
-            "any" => Ok(Column::new(&column_name, Type::Any, role, is_nullable)),
-            "boolean" => Ok(Column::new(&column_name, Type::Boolean, role, is_nullable)),
-            "datetime" => Ok(Column::new(&column_name, Type::Datetime, role, is_nullable)),
-            "decimal" => Ok(Column::new(&column_name, Type::Decimal, role, is_nullable)),
-            "double" => Ok(Column::new(&column_name, Type::Double, role, is_nullable)),
-            "integer" => Ok(Column::new(&column_name, Type::Integer, role, is_nullable)),
-            "numeric" => Ok(Column::new(
-                &column_name,
-                Type::default(),
-                role,
-                is_nullable,
-            )),
-            "string" | "text" | "varchar" => {
-                Ok(Column::new(&column_name, Type::String, role, is_nullable))
-            }
-            "unsigned" => Ok(Column::new(&column_name, Type::Unsigned, role, is_nullable)),
-            "array" => Ok(Column::new(&column_name, Type::Array, role, is_nullable)),
-            "uuid" => Ok(Column::new(&column_name, Type::Uuid, role, is_nullable)),
-            "map" => Ok(Column::new(&column_name, Type::Map, role, is_nullable)),
-            s => Err(Error::custom(format!("unsupported column type: {s}"))),
-        }
+        let ty = match column_type.as_str() {
+            "any" => DerivedType::new(Type::Any),
+            "boolean" => DerivedType::new(Type::Boolean),
+            "datetime" => DerivedType::new(Type::Datetime),
+            "decimal" => DerivedType::new(Type::Decimal),
+            "double" => DerivedType::new(Type::Double),
+            "integer" | "numeric" => DerivedType::new(Type::Integer),
+            "string" | "text" | "varchar" => DerivedType::new(Type::String),
+            "unsigned" => DerivedType::new(Type::Unsigned),
+            "array" => DerivedType::new(Type::Array),
+            "uuid" => DerivedType::new(Type::Uuid),
+            "map" => DerivedType::new(Type::Map),
+            "unknown" => DerivedType::unknown(),
+            s => return Err(Error::custom(format!("unsupported column type: {s}"))),
+        };
+        Ok(Column::new(&column_name, ty, role, is_nullable))
     }
 }
 
@@ -359,10 +402,10 @@ impl<'de> Deserialize<'de> for Column {
 impl Column {
     /// Column constructor.
     #[must_use]
-    pub fn new(n: &str, t: Type, role: ColumnRole, is_nullable: bool) -> Self {
+    pub fn new(n: &str, ty: DerivedType, role: ColumnRole, is_nullable: bool) -> Self {
         Column {
             name: n.into(),
-            r#type: t,
+            r#type: ty,
             role,
             is_nullable,
         }
diff --git a/sbroad/sbroad-core/src/ir/relation/tests.rs b/sbroad/sbroad-core/src/ir/relation/tests.rs
index d260af1421..f073e68835 100644
--- a/sbroad/sbroad-core/src/ir/relation/tests.rs
+++ b/sbroad/sbroad-core/src/ir/relation/tests.rs
@@ -98,8 +98,18 @@ fn table_seg_dno_bucket_id_column() {
             column_user_non_null(SmolStr::from("a"), Type::Boolean),
             column_user_non_null(SmolStr::from("b"), Type::Unsigned),
             column_user_non_null(SmolStr::from("c"), Type::String),
-            Column::new("bucket_id", Type::String, ColumnRole::Sharding, false),
-            Column::new("bucket_id2", Type::String, ColumnRole::Sharding, false),
+            Column::new(
+                "bucket_id",
+                DerivedType::new(Type::String),
+                ColumnRole::Sharding,
+                false,
+            ),
+            Column::new(
+                "bucket_id2",
+                DerivedType::new(Type::String),
+                ColumnRole::Sharding,
+                false,
+            ),
         ],
         &["b", "a"],
         &["b", "a"],
@@ -139,7 +149,12 @@ fn table_seg_compound_type_in_key() {
         Table::new_sharded(
             "t",
             vec![
-                Column::new("bucket_id", Type::Unsigned, ColumnRole::Sharding, false),
+                Column::new(
+                    "bucket_id",
+                    DerivedType::new(Type::Unsigned),
+                    ColumnRole::Sharding,
+                    false
+                ),
                 column_user_non_null(SmolStr::from("a"), Type::Array),
             ],
             &["a"],
diff --git a/sbroad/sbroad-core/src/ir/tests.rs b/sbroad/sbroad-core/src/ir/tests.rs
index 4582bb1820..55dd49ecc2 100644
--- a/sbroad/sbroad-core/src/ir/tests.rs
+++ b/sbroad/sbroad-core/src/ir/tests.rs
@@ -16,7 +16,7 @@ use smol_str::SmolStr;
 pub fn column_integer_user_non_null(name: SmolStr) -> Column {
     Column {
         name,
-        r#type: Type::Integer,
+        r#type: DerivedType::new(Type::Integer),
         role: ColumnRole::User,
         is_nullable: false,
     }
@@ -27,7 +27,7 @@ pub fn column_integer_user_non_null(name: SmolStr) -> Column {
 #[cfg(test)]
 pub fn vcolumn_integer_user_non_null() -> VTableColumn {
     VTableColumn {
-        r#type: Type::Integer,
+        r#type: DerivedType::new(Type::Integer),
         role: ColumnRole::User,
         is_nullable: false,
     }
@@ -43,7 +43,7 @@ pub fn vcolumn_integer_user_non_null() -> VTableColumn {
 pub fn column_user_non_null(name: SmolStr, r#type: Type) -> Column {
     Column {
         name,
-        r#type,
+        r#type: DerivedType::new(r#type),
         role: ColumnRole::User,
         is_nullable: false,
     }
@@ -54,7 +54,7 @@ pub fn column_user_non_null(name: SmolStr, r#type: Type) -> Column {
 #[cfg(test)]
 pub fn vcolumn_user_non_null(r#type: Type) -> VTableColumn {
     VTableColumn {
-        r#type,
+        r#type: DerivedType::new(r#type),
         role: ColumnRole::User,
         is_nullable: false,
     }
@@ -71,7 +71,7 @@ pub fn vcolumn_user_non_null(r#type: Type) -> VTableColumn {
 pub fn sharding_column() -> Column {
     Column {
         name: SmolStr::from("bucket_id"),
-        r#type: Type::Unsigned,
+        r#type: DerivedType::new(Type::Unsigned),
         role: ColumnRole::Sharding,
         is_nullable: true,
     }
@@ -83,7 +83,12 @@ fn get_node() {
 
     let t = Table::new_sharded(
         "t",
-        vec![Column::new("a", Type::Boolean, ColumnRole::User, false)],
+        vec![Column::new(
+            "a",
+            DerivedType::new(Type::Boolean),
+            ColumnRole::User,
+            false,
+        )],
         &["a"],
         &["a"],
         SpaceEngine::Memtx,
diff --git a/sbroad/sbroad-core/src/ir/transformation/equality_propagation.rs b/sbroad/sbroad-core/src/ir/transformation/equality_propagation.rs
index 1446e0ae70..68b970cb83 100644
--- a/sbroad/sbroad-core/src/ir/transformation/equality_propagation.rs
+++ b/sbroad/sbroad-core/src/ir/transformation/equality_propagation.rs
@@ -94,7 +94,7 @@ use crate::ir::helpers::RepeatableState;
 use crate::ir::node::expression::Expression;
 use crate::ir::node::{Constant, NodeId, Reference, ReferenceAsteriskSource, Row};
 use crate::ir::operator::Bool;
-use crate::ir::relation::Type;
+use crate::ir::relation::DerivedType;
 use crate::ir::transformation::merge_tuples::Chain;
 use crate::ir::transformation::OldNewTopIdPair;
 use crate::ir::value::{Trivalent, Value};
@@ -109,7 +109,7 @@ struct EqClassRef {
     targets: Option<Vec<usize>>,
     position: usize,
     parent: Option<NodeId>,
-    col_type: Type,
+    col_type: DerivedType,
     asterisk_source: Option<ReferenceAsteriskSource>,
 }
 
diff --git a/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs b/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
index 60f1ff74e2..8339b302fc 100644
--- a/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
+++ b/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
 use crate::collection;
-use crate::ir::relation::Type;
+use crate::ir::relation::{DerivedType, Type};
 use crate::ir::transformation::helpers::check_transformation;
 use crate::ir::value::Value;
 use crate::ir::Plan;
@@ -86,7 +86,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),
@@ -96,6 +96,10 @@ fn equality_propagation4() {
         ],
     );
 
+    println!(
+        "{}",
+        check_transformation(input, vec![], &derive_equalities).pattern
+    );
     assert_eq!(
         check_transformation(input, vec![], &derive_equalities),
         expected
@@ -113,8 +117,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"."d") = ("t"."b") and ("t"."b") = ("t"."c")"#,
-            r#"and ("t"."c") = ("t"."a")"#,
+            r#"and ("t"."b") = ("t"."c") and ("t"."c") = ("t"."d")"#,
+            r#"and ("t"."d") = ("t"."a")"#,
         ),
         vec![
             Value::from(1_u64),
@@ -124,6 +128,10 @@ fn equality_propagation5() {
         ],
     );
 
+    println!(
+        "{}",
+        check_transformation(input, vec![], &derive_equalities).pattern
+    );
     assert_eq!(
         check_transformation(input, vec![], &derive_equalities),
         expected
@@ -153,7 +161,7 @@ impl ColumnBuilder {
                 offset: 0,
                 arena_type: crate::ir::node::ArenaType::Arena64,
             }),
-            col_type: Type::Integer,
+            col_type: DerivedType::new(Type::Integer),
             asterisk_source: None,
         })
     }
diff --git a/sbroad/sbroad-core/src/ir/transformation/redistribution/groupby.rs b/sbroad/sbroad-core/src/ir/transformation/redistribution/groupby.rs
index 5e015f7983..599e304d3e 100644
--- a/sbroad/sbroad-core/src/ir/transformation/redistribution/groupby.rs
+++ b/sbroad/sbroad-core/src/ir/transformation/redistribution/groupby.rs
@@ -867,7 +867,7 @@ impl Plan {
         arguments: &[NodeId],
         local_alias: &str,
     ) -> Result<NodeId, SbroadError> {
-        let fun = Function {
+        let fun: Function = Function {
             name: kind.to_smolstr(),
             behavior: Behavior::Stable,
             func_type: kind.to_type(self, arguments)?,
@@ -1168,13 +1168,15 @@ impl Plan {
             };
             let position = child_map.get(local_alias)?;
             let col_type = self.get_expression_node(*expr_id)?.calculate_type(self)?;
-            if !col_type.is_scalar() {
-                return Err(SbroadError::Invalid(
-                    Entity::Type,
-                    Some(format_smolstr!(
-                        "add_final_groupby: GroupBy expr ({expr_id:?}) is not scalar ({col_type})!"
-                    )),
-                ));
+            if let Some(col_type) = col_type.get() {
+                if !col_type.is_scalar() {
+                    return Err(SbroadError::Invalid(
+                        Entity::Type,
+                        Some(format_smolstr!(
+                            "add_final_groupby: GroupBy expr ({expr_id:?}) is not scalar ({col_type})!"
+                        )),
+                    ));
+                }
             }
             let new_col = Reference {
                 position,
@@ -1268,14 +1270,16 @@ impl Plan {
                 };
                 let position = alias_to_pos_map.get(local_alias)?;
                 let col_type = self.get_expression_node(expr_id)?.calculate_type(self)?;
-                if !col_type.is_scalar() {
-                    return Err(SbroadError::Invalid(
-                        Entity::Type,
-                        Some(format_smolstr!(
-                            "patch_finals: expected scalar expression, found: {col_type}"
-                        )),
-                    ));
-                };
+                if let Some(col_type) = col_type.get() {
+                    if !col_type.is_scalar() {
+                        return Err(SbroadError::Invalid(
+                            Entity::Type,
+                            Some(format_smolstr!(
+                                "patch_finals: expected scalar expression, found: {col_type}"
+                            )),
+                        ));
+                    };
+                }
                 let new_ref = Reference {
                     parent: Some(rel_id),
                     targets: Some(vec![0]),
diff --git a/sbroad/sbroad-core/src/ir/value.rs b/sbroad/sbroad-core/src/ir/value.rs
index fbf2e7139b..c173924968 100644
--- a/sbroad/sbroad-core/src/ir/value.rs
+++ b/sbroad/sbroad-core/src/ir/value.rs
@@ -17,9 +17,11 @@ use tarantool::uuid::Uuid;
 use crate::error;
 use crate::errors::{Entity, SbroadError};
 use crate::executor::hash::ToHashString;
-use crate::ir::relation::Type;
+use crate::ir::relation::DerivedType;
 use crate::ir::value::double::Double;
 
+use super::relation::Type;
+
 #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone, PartialOrd, Ord)]
 pub struct Tuple(pub(crate) Vec<Value>);
 
@@ -880,7 +882,11 @@ impl Value {
     /// # Errors
     /// - the value cannot be cast to the given type.
     #[allow(clippy::too_many_lines)]
-    pub fn cast_and_encode(&self, column_type: &Type) -> Result<EncodedValue, SbroadError> {
+    pub fn cast_and_encode(&self, column_type: &DerivedType) -> Result<EncodedValue, SbroadError> {
+        let Some(column_type) = column_type.get() else {
+            return Ok(self.into());
+        };
+
         // First, try variants returning EncodedValue::Ref to avoid cloning.
         match (column_type, self) {
             (Type::Any, value) => return Ok(value.into()),
@@ -900,8 +906,8 @@ impl Value {
     }
 
     #[must_use]
-    pub fn get_type(&self) -> Type {
-        match self {
+    pub fn get_type(&self) -> DerivedType {
+        let ty = match self {
             Value::Unsigned(_) => Type::Unsigned,
             Value::Integer(_) => Type::Integer,
             Value::Datetime(_) => Type::Datetime,
@@ -911,8 +917,9 @@ impl Value {
             Value::String(_) => Type::String,
             Value::Tuple(_) => Type::Array,
             Value::Uuid(_) => Type::Uuid,
-            Value::Null => Type::default(),
-        }
+            Value::Null => return DerivedType::unknown(),
+        };
+        DerivedType::new(ty)
     }
 }
 
diff --git a/sbroad/sbroad-core/src/ir/value/tests.rs b/sbroad/sbroad-core/src/ir/value/tests.rs
index 4067eaf45c..ed39dbc5ed 100644
--- a/sbroad/sbroad-core/src/ir/value/tests.rs
+++ b/sbroad/sbroad-core/src/ir/value/tests.rs
@@ -18,7 +18,7 @@ fn uuid() {
 
     assert_eq!(Value::from(t_uid_1), v_uid);
     assert_eq!(format!("{}", v_uid), uid.to_string());
-    assert_eq!(v_uid.get_type(), Type::Uuid);
+    assert_eq!(v_uid.get_type(), DerivedType::new(Type::Uuid));
     assert_eq!(v_uid.eq(&Value::Uuid(t_uid_1)), Trivalent::True);
     assert_eq!(v_uid.eq(&Value::Uuid(t_uid_2)), Trivalent::False);
     assert_eq!(
@@ -35,7 +35,7 @@ fn uuid() {
     );
     assert_eq!(
         Value::String(uid.to_string())
-            .cast_and_encode(&Type::Uuid)
+            .cast_and_encode(&DerivedType::new(Type::Uuid))
             .is_ok(),
         true
     );
@@ -46,7 +46,7 @@ fn uuid() {
 fn uuid_negative() {
     assert_eq!(
         Value::String("hello".to_string())
-            .cast_and_encode(&Type::Uuid)
+            .cast_and_encode(&DerivedType::new(Type::Uuid))
             .unwrap_err(),
         SbroadError::Invalid(
             Entity::Value,
diff --git a/src/pgproto/backend/describe.rs b/src/pgproto/backend/describe.rs
index 05aaa48356..c747603836 100644
--- a/src/pgproto/backend/describe.rs
+++ b/src/pgproto/backend/describe.rs
@@ -15,7 +15,7 @@ use sbroad::{
             acl::Acl, block::Block, ddl::Ddl, expression::Expression, plugin::Plugin,
             relational::Relational, tcl::Tcl, Alias, GrantPrivilege, Node, RevokePrivilege,
         },
-        relation::Type as SbroadType,
+        relation::{DerivedType, Type as SbroadType},
         Plan,
     },
 };
@@ -297,16 +297,20 @@ impl MetadataColumn {
     }
 }
 
-fn pg_type_from_sbroad(sbroad: &SbroadType) -> PgResult<Type> {
-    match sbroad {
-        SbroadType::Integer | SbroadType::Unsigned => Ok(Type::INT8),
-        SbroadType::Map | SbroadType::Array | SbroadType::Any => Ok(Type::JSON),
-        SbroadType::String => Ok(Type::TEXT),
-        SbroadType::Boolean => Ok(Type::BOOL),
-        SbroadType::Double => Ok(Type::FLOAT8),
-        SbroadType::Decimal => Ok(Type::NUMERIC),
-        SbroadType::Uuid => Ok(Type::UUID),
-        SbroadType::Datetime => Ok(Type::TIMESTAMPTZ),
+fn pg_type_from_sbroad(sbroad: &DerivedType) -> PgResult<Type> {
+    if let Some(sbroad) = sbroad.get() {
+        match sbroad {
+            SbroadType::Integer | SbroadType::Unsigned => Ok(Type::INT8),
+            SbroadType::Map | SbroadType::Array | SbroadType::Any => Ok(Type::JSON),
+            SbroadType::String => Ok(Type::TEXT),
+            SbroadType::Boolean => Ok(Type::BOOL),
+            SbroadType::Double => Ok(Type::FLOAT8),
+            SbroadType::Decimal => Ok(Type::NUMERIC),
+            SbroadType::Uuid => Ok(Type::UUID),
+            SbroadType::Datetime => Ok(Type::TIMESTAMPTZ),
+        }
+    } else {
+        Ok(Type::UNKNOWN)
     }
 }
 
diff --git a/src/sql.rs b/src/sql.rs
index 9bb25c2336..af19f0cec8 100644
--- a/src/sql.rs
+++ b/src/sql.rs
@@ -53,7 +53,7 @@ use sbroad::ir::node::plugin::{
 };
 use sbroad::ir::node::Node;
 use sbroad::ir::operator::ConflictStrategy;
-use sbroad::ir::relation::Type;
+use sbroad::ir::relation::{DerivedType, Type};
 use sbroad::ir::tree::traversal::{LevelNode, PostOrderWithFilter, REL_CAPACITY};
 use sbroad::ir::value::{LuaValue, Value};
 use sbroad::ir::{Options, Plan as IrPlan};
@@ -438,15 +438,17 @@ pub fn dispatch(mut query: Query<RouterRuntime>) -> traft::Result<Tuple> {
                     let param_def = &routine.params[pos];
                     let param_type = Type::try_from(param_def.r#type)?;
                     // Check that the value has a correct type.
-                    if !value.get_type().is_castable_to(&param_type) {
-                        return Err(Error::Sbroad(SbroadError::Invalid(
-                            Entity::Routine,
-                            Some(format_smolstr!(
-                                "expected {} for parameter on position {pos}, got {}",
-                                param_def.r#type,
-                                value.get_type(),
-                            )),
-                        )));
+                    if let Some(ty) = value.get_type().get() {
+                        if !ty.is_castable_to(&param_type) {
+                            return Err(Error::Sbroad(SbroadError::Invalid(
+                                Entity::Routine,
+                                Some(format_smolstr!(
+                                    "expected {} for parameter on position {pos}, got {}",
+                                    param_def.r#type,
+                                    ty,
+                                )),
+                            )));
+                        }
                     }
                     params.push(value);
                 }
@@ -1158,7 +1160,8 @@ fn alter_system_ir_node_to_op_or_result(
             else {
                 return Err(Error::other(format!("unknown parameter: '{param_name}'")));
             };
-            let Ok(casted_value) = param_value.cast_and_encode(&expected_type) else {
+            let Ok(casted_value) = param_value.cast_and_encode(&DerivedType::new(expected_type))
+            else {
                 let actual_type = value_type_str(param_value);
                 return Err(Error::other(format!(
                     "invalid value for '{param_name}' expected {expected_type}, got {actual_type}",
diff --git a/src/sql/router.rs b/src/sql/router.rs
index edd51f6d72..6386d75f34 100644
--- a/src/sql/router.rs
+++ b/src/sql/router.rs
@@ -37,7 +37,7 @@ use crate::storage::{Clusterwide, ClusterwideTable};
 use sbroad::executor::engine::helpers::normalize_name_from_sql;
 use sbroad::executor::engine::Metadata;
 use sbroad::ir::function::Function;
-use sbroad::ir::relation::{space_pk_columns, Column, ColumnRole, Table, Type};
+use sbroad::ir::relation::{space_pk_columns, Column, ColumnRole, DerivedType, Table, Type};
 
 use crate::sql::storage::StorageRuntime;
 use crate::traft::node;
@@ -513,7 +513,7 @@ impl Metadata for RouterMetadata {
             };
             let column = Column {
                 name: col_name.to_smolstr(),
-                r#type: col_type,
+                r#type: DerivedType::new(col_type),
                 role,
                 is_nullable,
             };
-- 
GitLab


From 3efe5bfbdbe1d6741d8972a70f283ef773b13249 Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Mon, 27 Jan 2025 17:42:25 +0300
Subject: [PATCH 2/5] fix: recalculate types after params binding

Before this commit we wasn't recalculating types after parameters binding. This commit add additional tree traversal in order to fix it.
---
 .../arbitrary_expressions_test.lua            | 52 ++++++++++++
 .../test_app/test/integration/insert_test.lua | 43 +---------
 sbroad/sbroad-core/src/ir/api/parameter.rs    | 69 +++++++--------
 sbroad/sbroad-core/src/ir/expression/types.rs | 84 ++++++++++---------
 4 files changed, 128 insertions(+), 120 deletions(-)

diff --git a/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua b/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua
index 1c278e7b10..23cdfffd76 100644
--- a/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua
+++ b/sbroad/sbroad-cartridge/test_app/test/integration/arbitrary_expressions_test.lua
@@ -321,3 +321,55 @@ arbitrary_projection.test_arbitrary_valid = function()
         {7, 2},
     })
 end
+
+arbitrary_projection.test_values = function()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", { [[VALUES (?, ?, ?), (?, ?, ?)]], { 8, 8, box.NULL, 9, 9, 'hello' } })
+    t.assert_equals(err, nil)
+    t.assert_items_equals(r["metadata"], {
+        {name = "COLUMN_4", type = "integer"},
+        {name = "COLUMN_5", type = "integer"},
+        {name = "COLUMN_6", type = "string"},
+    })
+    t.assert_items_equals(r["rows"], { { 8, 8, box.NULL }, { 9, 9, 'hello' } })
+
+    local r, err = api:call("sbroad.execute", { [[VALUES (?, ?, ?), (?, ?, ?)]], { 8, 8, box.NULL, 9, 9, 'hello' } })
+    t.assert_equals(err, nil)
+    t.assert_items_equals(r["metadata"], {
+        {name = "COLUMN_4", type = "integer"},
+        {name = "COLUMN_5", type = "integer"},
+        {name = "COLUMN_6", type = "string"},
+    })
+    t.assert_items_equals(r["rows"], { { 8, 8, box.NULL }, { 9, 9, 'hello' } })
+
+    r, err = api:call(
+        "sbroad.execute",
+        { [[VALUES (?, ?, ?), (?, ?, ?)]], { 9, 9, 'hello', 8, 8, box.NULL } }
+    )
+    t.assert_equals(err, nil)
+    t.assert_items_equals(r["metadata"], {
+        {name = "COLUMN_4", type = "integer"},
+        {name = "COLUMN_5", type = "integer"},
+        {name = "COLUMN_6", type = "string"},
+    })
+    t.assert_items_equals(r["rows"], { { 9, 9, 'hello' }, { 8, 8, box.NULL } })
+
+    r, err = api:call("sbroad.execute", { [[VALUES (8, 8, null), (9, 9, 'hello')]], {} })
+    t.assert_equals(err, nil)
+    t.assert_items_equals(r["metadata"], {
+        {name = "COLUMN_4", type = "unsigned"},
+        {name = "COLUMN_5", type = "unsigned"},
+        {name = "COLUMN_6", type = "string"},
+    })
+    t.assert_items_equals(r["rows"], { { 8, 8, box.NULL }, { 9, 9, 'hello' } })
+
+    r, err = api:call("sbroad.execute", { [[VALUES (9, 9, 'hello'), (8, 8, null)]], {} })
+    t.assert_equals(err, nil)
+    t.assert_items_equals(r["metadata"], {
+        {name = "COLUMN_4", type = "unsigned"},
+        {name = "COLUMN_5", type = "unsigned"},
+        {name = "COLUMN_6", type = "string"},
+    })
+    t.assert_items_equals(r["rows"], { { 9, 9, 'hello' }, { 8, 8, box.NULL } })
+end
\ No newline at end of file
diff --git a/sbroad/sbroad-cartridge/test_app/test/integration/insert_test.lua b/sbroad/sbroad-cartridge/test_app/test/integration/insert_test.lua
index d2d2bb149c..c4fc74fd30 100644
--- a/sbroad/sbroad-cartridge/test_app/test/integration/insert_test.lua
+++ b/sbroad/sbroad-cartridge/test_app/test/integration/insert_test.lua
@@ -235,51 +235,10 @@ g.test_insert_6 = function()
     })
 end
 
--- TODO(ars): this test fails. What also fails is a simple query like: values (1, 'hello')...
--- check type derivation for null column in the first row of the VALUES operator
 g.test_insert_7 = function()
     local api = cluster:server("api-1").net_box
 
-    local r, err = api:call("sbroad.execute", { [[VALUES (?, ?, ?), (?, ?, ?)]], { 8, 8, box.NULL, 9, 9, 'hello' } })
-    t.assert_equals(err, nil)
-    t.assert_items_equals(r["metadata"], {
-        {name = "COLUMN_4", type = "integer"},
-        {name = "COLUMN_5", type = "integer"},
-        {name = "COLUMN_6", type = "integer"},
-    })
-    t.assert_items_equals(r["rows"], { { 8, 8, box.NULL }, { 9, 9, 'hello' } })
-
-    r, err = api:call(
-        "sbroad.execute",
-        { [[VALUES (?, ?, ?), (?, ?, ?)]], { 9, 9, 'hello', 8, 8, box.NULL } }
-    )
-    t.assert_equals(err, nil)
-    t.assert_items_equals(r["metadata"], {
-        {name = "COLUMN_4", type = "integer"},
-        {name = "COLUMN_5", type = "integer"},
-        {name = "COLUMN_6", type = "integer"},
-    })
-    t.assert_items_equals(r["rows"], { { 9, 9, 'hello' }, { 8, 8, box.NULL } })
-
-    r, err = api:call("sbroad.execute", { [[VALUES (8, 8, null), (9, 9, 'hello')]], {} })
-    t.assert_equals(err, nil)
-    t.assert_items_equals(r["metadata"], {
-        {name = "COLUMN_4", type = "unsigned"},
-        {name = "COLUMN_5", type = "unsigned"},
-        {name = "COLUMN_6", type = "string"},
-    })
-    t.assert_items_equals(r["rows"], { { 8, 8, box.NULL }, { 9, 9, 'hello' } })
-
-    r, err = api:call("sbroad.execute", { [[VALUES (9, 9, 'hello'), (8, 8, null)]], {} })
-    t.assert_equals(err, nil)
-    t.assert_items_equals(r["metadata"], {
-        {name = "COLUMN_4", type = "unsigned"},
-        {name = "COLUMN_5", type = "unsigned"},
-        {name = "COLUMN_6", type = "integer"},
-    })
-    t.assert_items_equals(r["rows"], { { 9, 9, 'hello' }, { 8, 8, box.NULL } })
-
-    r, err = api:call("sbroad.execute", { [[INSERT INTO "space_simple_shard_key"
+    local r, err = api:call("sbroad.execute", { [[INSERT INTO "space_simple_shard_key"
     ("sysOp", "id", "name") VALUES (?, ?, ?), (?, ?, ?)]], { 8, 8, box.NULL, 9, 9, 'hello' } })
     t.assert_equals(err, nil)
     t.assert_equals(r, {row_count = 2})
diff --git a/sbroad/sbroad-core/src/ir/api/parameter.rs b/sbroad/sbroad-core/src/ir/api/parameter.rs
index e79e69a175..17bfd45237 100644
--- a/sbroad/sbroad-core/src/ir/api/parameter.rs
+++ b/sbroad/sbroad-core/src/ir/api/parameter.rs
@@ -66,6 +66,9 @@ fn get_param_value(
 impl<'binder> ParamsBinder<'binder> {
     fn new(plan: &'binder mut Plan, mut values: Vec<Value>) -> Result<Self, SbroadError> {
         let capacity = plan.nodes.len();
+
+        // Note: `need_output` is set to false for `subtree_iter` specially to avoid traversing
+        //       the same nodes twice. See `update_values_row` for more info.
         let mut tree = PostOrder::with_capacity(|node| plan.subtree_iter(node, false), capacity);
         let top_id = plan.get_top()?;
         tree.populate_nodes(top_id);
@@ -402,24 +405,6 @@ impl<'binder> ParamsBinder<'binder> {
     /// Replace parameters in the plan.
     #[allow(clippy::too_many_lines)]
     fn bind_params(&mut self) -> Result<(), SbroadError> {
-        let mut exprs_to_set_ref_type: HashMap<NodeId, DerivedType> = HashMap::new();
-
-        for LevelNode(_, id) in &self.nodes {
-            // Before binding, references that referred to
-            // parameters had scalar type (by default),
-            // but in fact they may refer to different stuff.
-            if let Node::Expression(expr) = self.plan.get_node(*id)? {
-                if let Expression::Reference { .. } = expr {
-                    exprs_to_set_ref_type.insert(*id, expr.recalculate_type(self.plan)?);
-                    continue;
-                }
-            }
-        }
-        for (id, new_type) in exprs_to_set_ref_type {
-            let mut expr = self.plan.get_mut_expression_node(id)?;
-            expr.set_ref_type(new_type);
-        }
-
         // Len of `value_ids` - `param_index` = param index we are currently binding.
         let mut param_index = self.value_ids.len();
 
@@ -586,6 +571,31 @@ impl<'binder> ParamsBinder<'binder> {
         }
         Ok(())
     }
+
+    fn recalculate_ref_types(&mut self) -> Result<(), SbroadError> {
+        for LevelNode(_, id) in &self.nodes {
+            // Before binding, references that referred to
+            // parameters had an unknown types,
+            // but in fact they should have the types of given parameters.
+            let new_type = if let Node::Expression(ref mut expr @ Expression::Reference(_)) =
+                self.plan.get_node(*id)?
+            {
+                Some(expr.recalculate_ref_type(self.plan)?)
+            } else {
+                None
+            };
+
+            if let Some(new_type) = new_type {
+                let MutNode::Expression(ref mut expr @ MutExpression::Reference { .. }) =
+                    self.plan.get_mut_node(*id)?
+                else {
+                    panic!("Reference expected to set recalculated type")
+                };
+                expr.set_ref_type(new_type);
+            }
+        }
+        Ok(())
+    }
 }
 
 impl Plan {
@@ -600,12 +610,6 @@ impl Plan {
 
     /// Bind params related to `Option` clause.
     /// Returns the number of params binded to options.
-    ///
-    /// # Errors
-    /// - User didn't provide parameter value for corresponding option parameter
-    ///
-    /// # Panics
-    /// - Plan is inconsistent state
     pub fn bind_option_params(&mut self, values: &mut Vec<Value>) -> usize {
         // Bind parameters in options to values.
         // Because the Option clause is the last clause in the
@@ -634,9 +638,7 @@ 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
@@ -659,13 +661,9 @@ impl Plan {
 
     /// Synchronize values row output with the data tuple after parameter binding.
     ///
-    /// # Errors
-    /// - Node is not values row
-    /// - Output and data tuples have different number of columns
-    /// - Output is not a row of aliases
-    ///
-    /// # Panics
-    /// - Plan is inconsistent state
+    /// ValuesRow fields `data` and `output` are referencing the same nodes. We exclude
+    /// their output from nodes traversing. And in case parameters are met under ValuesRow, we don't update
+    /// references in its output. That why we have to traverse the tree one more time fixing output.
     pub fn update_values_row(&mut self, id: NodeId) -> Result<(), SbroadError> {
         let values_row = self.get_node(id)?;
         let (output_id, data_id) =
@@ -697,10 +695,6 @@ impl Plan {
     /// Substitute parameters to the plan.
     /// The purpose of this function is to find every `Parameter` node and replace it
     /// with `Expression::Constant` (under the row).
-    ///
-    /// # Errors
-    /// - Invalid amount of parameters.
-    /// - Internal errors.
     #[allow(clippy::too_many_lines)]
     pub fn bind_params(&mut self, values: Vec<Value>) -> Result<(), SbroadError> {
         if values.is_empty() {
@@ -739,6 +733,7 @@ impl Plan {
         binder.cover_params_with_rows()?;
         binder.bind_params()?;
         binder.update_value_rows()?;
+        binder.recalculate_ref_types()?;
 
         Ok(())
     }
diff --git a/sbroad/sbroad-core/src/ir/expression/types.rs b/sbroad/sbroad-core/src/ir/expression/types.rs
index 8f6736c94f..3cd5b7e908 100644
--- a/sbroad/sbroad-core/src/ir/expression/types.rs
+++ b/sbroad/sbroad-core/src/ir/expression/types.rs
@@ -2,6 +2,7 @@ use smol_str::{format_smolstr, ToSmolStr};
 
 use crate::{
     errors::{Entity, SbroadError, TypeError},
+    executor::vtable::calculate_unified_types,
     ir::{
         relation::{DerivedType, Type},
         Plan,
@@ -207,54 +208,55 @@ impl Expression<'_> {
     /// where we can't calculate type of
     /// upper reference, because we don't know what value will be
     /// passed as an argument.
-    /// When `resolve_metadata` is called references are typed with `Scalar`.
+    /// When `resolve_metadata` is called references have unknown type.
     /// When `bind_params` is called references types are refined.
-    ///
-    /// # Errors
-    /// - if the reference is invalid;
-    pub fn recalculate_type(&self, plan: &Plan) -> Result<DerivedType, SbroadError> {
-        if let Expression::Reference(Reference {
+    pub fn recalculate_ref_type(&self, plan: &Plan) -> Result<DerivedType, SbroadError> {
+        let prev_type = self.calculate_type(plan)?;
+        let Expression::Reference(Reference {
             parent,
             targets,
             position,
             ..
         }) = self
-        {
-            let parent_id = parent.ok_or_else(|| {
-                SbroadError::Invalid(
-                    Entity::Expression,
-                    Some("reference expression has no parent".to_smolstr()),
-                )
-            })?;
-            let parent_rel = plan.get_relation_node(parent_id)?;
-            // We are interested only in the first target, because:
-            // - union all relies on the first child type;
-            // - scan has no children (and the space column type can't change anyway);
-            if let Some(Some(target)) = targets.as_ref().map(|targets| targets.first()) {
-                let target_children = parent_rel.children();
-                let target_rel_id = *target_children.get(*target).ok_or_else(|| {
-                    SbroadError::Invalid(
-                        Entity::Expression,
-                        Some(format_smolstr!(
-                            "reference expression has no target relation at position {target}"
-                        )),
-                    )
-                })?;
-                let target_rel = plan.get_relation_node(target_rel_id)?;
-                let columns = plan.get_row_list(target_rel.output())?;
-                let column_id = *columns.get(*position).ok_or_else(|| {
-                    SbroadError::Invalid(
-                        Entity::Expression,
-                        Some(format_smolstr!(
-                            "reference expression has no target column at position {position}"
-                        )),
-                    )
-                })?;
-                let col_expr = plan.get_expression_node(column_id)?;
-                return col_expr.calculate_type(plan);
-            }
+        else {
+            return Ok(prev_type);
+        };
+
+        let Some(targets) = targets else {
+            // No need to recalculate types for Scan node references.
+            return Ok(prev_type);
+        };
+
+        let parent_id = parent.ok_or_else(|| {
+            SbroadError::Invalid(
+                Entity::Expression,
+                Some("reference expression has no parent".to_smolstr()),
+            )
+        })?;
+        let parent_rel = plan.get_relation_node(parent_id)?;
+        let rel_children: crate::ir::api::children::Children<'_> = parent_rel.children();
+
+        let mut types = Vec::new();
+        for target_index in targets {
+            let target_rel_id = *rel_children.get(*target_index).unwrap_or_else(|| {
+                panic!("reference expression has no target relation at position {target_index}")
+            });
+
+            let target_rel = plan.get_relation_node(target_rel_id)?;
+            let columns = plan.get_row_list(target_rel.output())?;
+            let column_id = *columns.get(*position).unwrap_or_else(|| {
+                panic!("reference expression has no target column at position {position}")
+            });
+            let col_expr = plan.get_expression_node(column_id)?;
+            let ty: DerivedType = col_expr.calculate_type(plan)?;
+            types.push(vec![ty])
         }
-        self.calculate_type(plan)
+
+        let unified_res = calculate_unified_types(&types)?;
+        let (_, unified_type) = unified_res.first().expect(
+            "Vec for reference unified type recalculation should consists of a single column.",
+        );
+        Ok(*unified_type)
     }
 }
 
-- 
GitLab


From 79c9745f770071819e22681288dc985d1e07a9e1 Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Mon, 27 Jan 2025 20:36:32 +0300
Subject: [PATCH 3/5] feat: add a test for VALUES materialization

---
 test/int/test_sql.py | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/test/int/test_sql.py b/test/int/test_sql.py
index 2700bce809..c9b3436bcd 100644
--- a/test/int/test_sql.py
+++ b/test/int/test_sql.py
@@ -1899,6 +1899,33 @@ def test_check_format(cluster: Cluster):
     assert dml["row_count"] == 2
 
 
+def test_values(cluster: Cluster):
+    # Initially test scenarios (presented below) were described in the issue:
+    # https://git.picodata.io/core/picodata/-/issues/1278.
+    cluster.deploy(instance_count=2)
+    i1, _ = cluster.instances
+
+    ddl = i1.sql(""" create table t (a int primary key, b text) """)
+    assert ddl["row_count"] == 1
+
+    dml = i1.sql(""" insert into t values (1, (values(?))), (2, 'hi') """, None)
+    assert dml["row_count"] == 2
+
+    data = i1.sql("""select * from t """)
+    assert data == [[1, None], [2, "hi"]]
+
+    ddl = i1.sql(
+        """ create table gt (a unsigned primary key, b integer) distributed globally """
+    )
+    assert ddl["row_count"] == 1
+
+    dml = i1.sql(""" insert into gt values (0, -9), (1, 9) """)
+    assert dml["row_count"] == 2
+
+    data = i1.sql("""select * from gt """)
+    assert data == [[0, -9], [1, 9]]
+
+
 def test_insert(cluster: Cluster):
     cluster.deploy(instance_count=2)
     i1, _ = cluster.instances
-- 
GitLab


From f4898262b37fd985cb52b88f080bdebe1fdef48b Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Tue, 28 Jan 2025 14:34:57 +0300
Subject: [PATCH 4/5] chore: remove useless printlns in sql explain tests

printlns were previously used in order to fix expected explain more easily, this commit removes them
---
 .../sbroad-core/src/backend/sql/ir/tests.rs   |  1 -
 .../src/executor/tests/exec_plan.rs           |  2 -
 .../sbroad-core/src/executor/vtable/tests.rs  |  1 -
 .../sbroad-core/src/frontend/sql/ir/tests.rs  | 51 ++-----------------
 .../src/frontend/sql/ir/tests/funcs.rs        |  1 -
 .../src/frontend/sql/ir/tests/global.rs       |  1 -
 .../equality_propagation/tests.rs             |  8 ---
 7 files changed, 3 insertions(+), 62 deletions(-)

diff --git a/sbroad/sbroad-core/src/backend/sql/ir/tests.rs b/sbroad/sbroad-core/src/backend/sql/ir/tests.rs
index 86c7b7ef22..f699ec5196 100644
--- a/sbroad/sbroad-core/src/backend/sql/ir/tests.rs
+++ b/sbroad/sbroad-core/src/backend/sql/ir/tests.rs
@@ -31,7 +31,6 @@ fn check_sql_with_snapshot(
     let nodes = ordered.to_syntax_data().unwrap();
     let (sql, _) = ex_plan.to_sql(&nodes, "test", None).unwrap();
 
-    println!("{}", sql.pattern);
     assert_eq!(expected, sql,);
 }
 
diff --git a/sbroad/sbroad-core/src/executor/tests/exec_plan.rs b/sbroad/sbroad-core/src/executor/tests/exec_plan.rs
index 50cec9d89a..99b321c93b 100644
--- a/sbroad/sbroad-core/src/executor/tests/exec_plan.rs
+++ b/sbroad/sbroad-core/src/executor/tests/exec_plan.rs
@@ -223,7 +223,6 @@ fn exec_plan_subtree_aggregates() {
         panic!("Expected MotionPolicy::Segment for local aggregation stage");
     };
 
-    println!("{}", sql.pattern);
     assert_eq!(
         sql,
         PatternWithParams::new(
@@ -243,7 +242,6 @@ max ("T1"."id") as "max_3296" FROM "test_space" as "T1" GROUP BY "T1"."sys_op",
 
     // Check main query
     let sql = get_sql_from_execution_plan(exec_plan, top_id, Snapshot::Oldest, TEMPLATE);
-    println!("{}", sql.pattern);
     assert_eq!(
         sql,
         PatternWithParams::new(
diff --git a/sbroad/sbroad-core/src/executor/vtable/tests.rs b/sbroad/sbroad-core/src/executor/vtable/tests.rs
index e679bafaac..9aa006b9ac 100644
--- a/sbroad/sbroad-core/src/executor/vtable/tests.rs
+++ b/sbroad/sbroad-core/src/executor/vtable/tests.rs
@@ -384,7 +384,6 @@ fn vtable_values_types_casting_two_tuples_err() {
     actual_vtable.add_tuple(vec![Value::String("name".into())]);
     let vtable_types = actual_vtable.get_types();
     let err = calculate_unified_types(&vtable_types).unwrap_err();
-    println!("{}", err);
     assert_eq!(
         true,
         err.to_string()
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs
index b9096891e3..f115292684 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -1457,7 +1457,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1483,7 +1482,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1546,7 +1544,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1580,7 +1577,6 @@ execution options:
 "#,
     );
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -1662,7 +1658,6 @@ execution options:
 "#,
     );
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -1687,7 +1682,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -1736,7 +1730,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1805,7 +1798,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1827,7 +1819,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1849,7 +1840,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1871,7 +1861,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1893,7 +1882,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1947,7 +1935,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -1970,7 +1957,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2009,7 +1995,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2032,7 +2017,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2056,7 +2040,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2078,7 +2061,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2207,7 +2189,7 @@ execution options:
     vtable_max_rows = 42
 "#,
     );
-    println!("{}", plan.as_explain().unwrap());
+
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2359,7 +2341,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2388,7 +2369,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2420,7 +2400,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2443,7 +2422,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2470,7 +2448,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 
     let input = r#"SELECT "b", "a" from "t"
@@ -2495,7 +2472,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2522,7 +2498,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2552,7 +2527,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2576,7 +2550,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2600,7 +2573,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2623,7 +2595,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2647,7 +2618,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -2743,7 +2713,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -2777,7 +2746,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -2810,7 +2778,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2867,7 +2834,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2891,7 +2857,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2914,7 +2879,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -2963,7 +2927,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -3014,7 +2977,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -3047,7 +3009,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -3069,7 +3030,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -3095,7 +3055,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -3160,7 +3119,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -3182,7 +3140,6 @@ execution options:
     );
 
     assert_eq!(expected_explain, plan.as_explain().unwrap());
-    println!("{}", plan.as_explain().unwrap());
 }
 
 #[test]
@@ -3220,7 +3177,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -3240,7 +3196,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -3598,7 +3553,7 @@ execution options:
     vtable_max_rows = 5000
 "#,
     );
-    println!("{}", plan.as_explain().unwrap());
+
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
@@ -4154,7 +4109,7 @@ execution options:
     vtable_max_rows = 5000
 "#,
     );
-    println!("{}", plan.as_explain().unwrap());
+
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests/funcs.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests/funcs.rs
index 6445f3597d..cd5487c63f 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests/funcs.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests/funcs.rs
@@ -6,7 +6,6 @@ fn lower_upper() {
     let input = r#"select upper(lower('a' || 'B')), upper(a) from t1"#;
 
     let plan = sql_to_optimized_ir(input, vec![]);
-    println!("{}", plan.as_explain().unwrap());
 
     let expected_explain = String::from(
         r#"projection (upper((lower((ROW('a'::string) || ROW('B'::string)))::string))::string -> "col_1", upper(("t1"."a"::string))::string -> "col_2")
diff --git a/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs b/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs
index a523f489bc..674a5edd57 100644
--- a/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs
+++ b/sbroad/sbroad-core/src/frontend/sql/ir/tests/global.rs
@@ -827,7 +827,6 @@ execution options:
 "#,
     );
 
-    println!("{}", plan.as_explain().unwrap());
     assert_eq!(expected_explain, plan.as_explain().unwrap());
 }
 
diff --git a/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs b/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
index 8339b302fc..9d001af770 100644
--- a/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
+++ b/sbroad/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
@@ -96,10 +96,6 @@ fn equality_propagation4() {
         ],
     );
 
-    println!(
-        "{}",
-        check_transformation(input, vec![], &derive_equalities).pattern
-    );
     assert_eq!(
         check_transformation(input, vec![], &derive_equalities),
         expected
@@ -128,10 +124,6 @@ fn equality_propagation5() {
         ],
     );
 
-    println!(
-        "{}",
-        check_transformation(input, vec![], &derive_equalities).pattern
-    );
     assert_eq!(
         check_transformation(input, vec![], &derive_equalities),
         expected
-- 
GitLab


From 33a1a47827b46bde844f11f6f98a9813b90bcf05 Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Wed, 29 Jan 2025 13:02:48 +0300
Subject: [PATCH 5/5] chore: refactoring

---
 sbroad/sbroad-core/src/executor/result.rs     |  6 +----
 sbroad/sbroad-core/src/executor/vtable.rs     |  4 +---
 sbroad/sbroad-core/src/ir/expression/types.rs |  9 +-------
 src/pgproto/backend/describe.rs               | 22 +++++++++----------
 test/int/test_sql.py                          |  2 +-
 5 files changed, 15 insertions(+), 28 deletions(-)

diff --git a/sbroad/sbroad-core/src/executor/result.rs b/sbroad/sbroad-core/src/executor/result.rs
index 212aef4942..8c91998925 100644
--- a/sbroad/sbroad-core/src/executor/result.rs
+++ b/sbroad/sbroad-core/src/executor/result.rs
@@ -164,11 +164,7 @@ impl ProducerResult {
                     continue;
                 };
 
-                let types_equal = if let Some(value_type) = value.get_type().get() {
-                    value_type == column_ty
-                } else {
-                    false
-                };
+                let types_equal = *value.get_type().get() == Some(*column_ty);
 
                 let casted_value = if types_equal {
                     value
diff --git a/sbroad/sbroad-core/src/executor/vtable.rs b/sbroad/sbroad-core/src/executor/vtable.rs
index 50840879ea..62f1fb0eae 100644
--- a/sbroad/sbroad-core/src/executor/vtable.rs
+++ b/sbroad/sbroad-core/src/executor/vtable.rs
@@ -805,9 +805,7 @@ pub fn calculate_unified_types(
         Ok(())
     };
 
-    let mut unified_types: Vec<DerivedType> = iter::repeat(DerivedType::unknown())
-        .take(columns_len)
-        .collect();
+    let mut unified_types: Vec<DerivedType> = vec![DerivedType::unknown(); columns_len];
 
     for type_tuple in types {
         for (i, ty) in type_tuple.iter().enumerate() {
diff --git a/sbroad/sbroad-core/src/ir/expression/types.rs b/sbroad/sbroad-core/src/ir/expression/types.rs
index 3cd5b7e908..e2d8475511 100644
--- a/sbroad/sbroad-core/src/ir/expression/types.rs
+++ b/sbroad/sbroad-core/src/ir/expression/types.rs
@@ -116,14 +116,7 @@ impl Expression<'_> {
 
                 let (left_type, right_type) = match (left_type.get(), right_type.get()) {
                     (Some(l_t), Some(r_t)) => (l_t, r_t),
-                    _ => {
-                        return Err(SbroadError::Invalid(
-                            Entity::Expression,
-                            Some(format_smolstr!(
-                                "Null type is not supported for arithmetic expression"
-                            )),
-                        ))
-                    }
+                    _ => return Ok(DerivedType::unknown()),
                 };
 
                 let res = match (left_type, right_type) {
diff --git a/src/pgproto/backend/describe.rs b/src/pgproto/backend/describe.rs
index c747603836..7eebde8305 100644
--- a/src/pgproto/backend/describe.rs
+++ b/src/pgproto/backend/describe.rs
@@ -297,20 +297,20 @@ impl MetadataColumn {
     }
 }
 
-fn pg_type_from_sbroad(sbroad: &DerivedType) -> PgResult<Type> {
+fn pg_type_from_sbroad(sbroad: &DerivedType) -> Type {
     if let Some(sbroad) = sbroad.get() {
         match sbroad {
-            SbroadType::Integer | SbroadType::Unsigned => Ok(Type::INT8),
-            SbroadType::Map | SbroadType::Array | SbroadType::Any => Ok(Type::JSON),
-            SbroadType::String => Ok(Type::TEXT),
-            SbroadType::Boolean => Ok(Type::BOOL),
-            SbroadType::Double => Ok(Type::FLOAT8),
-            SbroadType::Decimal => Ok(Type::NUMERIC),
-            SbroadType::Uuid => Ok(Type::UUID),
-            SbroadType::Datetime => Ok(Type::TIMESTAMPTZ),
+            SbroadType::Integer | SbroadType::Unsigned => Type::INT8,
+            SbroadType::Map | SbroadType::Array | SbroadType::Any => Type::JSON,
+            SbroadType::String => Type::TEXT,
+            SbroadType::Boolean => Type::BOOL,
+            SbroadType::Double => Type::FLOAT8,
+            SbroadType::Decimal => Type::NUMERIC,
+            SbroadType::Uuid => Type::UUID,
+            SbroadType::Datetime => Type::TIMESTAMPTZ,
         }
     } else {
-        Ok(Type::UNKNOWN)
+        Type::UNKNOWN
     }
 }
 
@@ -333,7 +333,7 @@ fn dql_output_format(ir: &Plan) -> PgResult<Vec<MetadataColumn>> {
             )
             .into());
         };
-        let ty = pg_type_from_sbroad(&column_type)?;
+        let ty = pg_type_from_sbroad(&column_type);
         metadata.push(MetadataColumn::new(column_name, ty));
     }
     Ok(metadata)
diff --git a/test/int/test_sql.py b/test/int/test_sql.py
index c9b3436bcd..aec9eae67b 100644
--- a/test/int/test_sql.py
+++ b/test/int/test_sql.py
@@ -1912,7 +1912,7 @@ def test_values(cluster: Cluster):
     assert dml["row_count"] == 2
 
     data = i1.sql("""select * from t """)
-    assert data == [[1, None], [2, "hi"]]
+    assert sorted(data) == [[1, None], [2, "hi"]]
 
     ddl = i1.sql(
         """ create table gt (a unsigned primary key, b integer) distributed globally """
-- 
GitLab