diff --git a/Cargo.lock b/Cargo.lock
index df938505168d476abbea46a842ef0f0bde1528b5..44532d2a198ea60e2516d380ba148092169394bf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1073,9 +1073,9 @@ dependencies = [
 
 [[package]]
 name = "rmp-serde"
-version = "1.0.0"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3eedffbfcc6a428f230c04baf8f59bd73c1781361e4286111fe900849aaddaf"
+checksum = "25786b0d276110195fa3d6f3f31299900cf71dfbd6c28450f3f58a0e7f7a347e"
 dependencies = [
  "byteorder",
  "rmp",
@@ -1162,7 +1162,7 @@ dependencies = [
  "pest_derive",
  "pretty_assertions",
  "rand",
- "rmp-serde 1.0.0",
+ "rmp-serde 1.1.0",
  "sbroad-proc",
  "serde",
  "serde_yaml",
@@ -1342,8 +1342,8 @@ dependencies = [
 
 [[package]]
 name = "tarantool"
-version = "4.0.0"
-source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#b3f5303309ee5f07ba71d830d9f04df633895e14"
+version = "5.0.0"
+source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#dbebcc0aace0264b7ba9569e9a8a672b972c95ea"
 dependencies = [
  "async-trait",
  "base64",
@@ -1357,7 +1357,7 @@ dependencies = [
  "once_cell",
  "refpool",
  "rmp",
- "rmp-serde 1.0.0",
+ "rmp-serde 1.1.0",
  "rmpv",
  "serde",
  "serde_bytes",
@@ -1374,7 +1374,7 @@ dependencies = [
 [[package]]
 name = "tarantool-proc"
 version = "3.0.0"
-source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#b3f5303309ee5f07ba71d830d9f04df633895e14"
+source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#dbebcc0aace0264b7ba9569e9a8a672b972c95ea"
 dependencies = [
  "darling",
  "proc-macro-error",
@@ -1462,8 +1462,8 @@ dependencies = [
 
 [[package]]
 name = "tlua"
-version = "2.0.0"
-source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#b3f5303309ee5f07ba71d830d9f04df633895e14"
+version = "3.1.0"
+source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#dbebcc0aace0264b7ba9569e9a8a672b972c95ea"
 dependencies = [
  "libc",
  "serde",
@@ -1474,7 +1474,7 @@ dependencies = [
 [[package]]
 name = "tlua-derive"
 version = "0.2.0"
-source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#b3f5303309ee5f07ba71d830d9f04df633895e14"
+source = "git+https://git.picodata.io/picodata/picodata/tarantool-module.git#dbebcc0aace0264b7ba9569e9a8a672b972c95ea"
 dependencies = [
  "proc-macro2 1.0.79",
  "quote 1.0.35",
diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf
index 0af4fb634db2274642223159df75b3f2c3fc2893..4b34908956e776795b59864bddf3e0671313d852 100644
--- a/doc/sql/query.ebnf
+++ b/doc/sql/query.ebnf
@@ -32,6 +32,7 @@ expression  ::= (table '.')? column
                | ('(' (expression(',' expression)*) ')')
                | 'NOT' expression
                | '(' expression ')'
+               | 'TO_DATE' '(' expression',' format ')'
                | 'TRIM' '(' ((('LEADING' | 'TRAILING' | 'BOTH')? expression) | ('LEADING' | 'TRAILING' | 'BOTH')) 'FROM' expression ')'
 aggregate   ::= ('AVG' | 'COUNT' | 'MAX' | 'MIN' | 'SUM' | 'TOTAL') '(' expression ')'
                | 'GROUP_CONCAT' '(' expression ','  "'" string "'" ')'
@@ -154,6 +155,7 @@ drop_table     ::= 'DROP' 'TABLE' table
 drop_role      ::= 'DROP' 'ROLE' role
 drop_user      ::= 'DROP' 'USER' user
 type        ::= ('BOOL' | 'BOOLEAN')
+                | 'DATETIME'
                 | 'DECIMAL'
                 | 'DOUBLE'
                 | ('INT' | 'INTEGER')
diff --git a/sbroad-cartridge/cartridge/roles/sbroad-storage.lua b/sbroad-cartridge/cartridge/roles/sbroad-storage.lua
index a8bc7cb39449993aa35d829858d214e4ed9dcca6..9e6ef2a87fa6820794af760c41251e5647175103 100644
--- a/sbroad-cartridge/cartridge/roles/sbroad-storage.lua
+++ b/sbroad-cartridge/cartridge/roles/sbroad-storage.lua
@@ -1,5 +1,6 @@
 local sbroad_common = require('sbroad.init')
 local sbroad_storage = require('sbroad.storage')
+local sbroad_builtins = require('sbroad.builtins')
 
 local function init(opts) -- luacheck: no unused args
     if rawget(_G, 'sbroad') == nil then
@@ -9,6 +10,9 @@ local function init(opts) -- luacheck: no unused args
     _G.sbroad.calculate_bucket_id = sbroad_common.calculate_bucket_id
 
     sbroad_common.init(opts.is_master)
+    if opts.is_master then
+        sbroad_builtins.init()
+    end
 
     return true
 end
diff --git a/sbroad-cartridge/src/cartridge/config.rs b/sbroad-cartridge/src/cartridge/config.rs
index aa4704d58b386622c0592cc57dc6bdefa368b978..e8b0667f00ea5ec7b98e6cbee0944e44ce34344e 100644
--- a/sbroad-cartridge/src/cartridge/config.rs
+++ b/sbroad-cartridge/src/cartridge/config.rs
@@ -8,7 +8,7 @@ use yaml_rust::{Yaml, YamlLoader};
 
 use sbroad::errors::{Entity, SbroadError};
 use sbroad::executor::engine::helpers::{normalize_name_from_schema, normalize_name_from_sql};
-use sbroad::executor::engine::Metadata;
+use sbroad::executor::engine::{get_builtin_functions, Metadata};
 use sbroad::executor::lru::DEFAULT_CAPACITY;
 use sbroad::ir::function::Function;
 use sbroad::ir::relation::{Column, ColumnRole, SpaceEngine, Table, Type};
@@ -45,12 +45,18 @@ impl Default for RouterConfiguration {
 impl RouterConfiguration {
     #[must_use]
     pub fn new() -> Self {
+        let builtins = get_builtin_functions();
+        let mut functions = HashMap::with_capacity(builtins.len());
+        for f in builtins {
+            functions.insert(f.name.clone(), f.clone());
+        }
+
         RouterConfiguration {
             waiting_timeout: 360,
             cache_capacity: DEFAULT_CAPACITY,
             tables: HashMap::new(),
             sharding_column: SmolStr::default(),
-            functions: HashMap::new(),
+            functions,
         }
     }
 
diff --git a/sbroad-cartridge/src/init.lua b/sbroad-cartridge/src/init.lua
index bd6fd2e4de3536c08abdf0c14b4735f3c4012b3e..dc81079cf579e6ba8570a0330497f4d67835857d 100644
--- a/sbroad-cartridge/src/init.lua
+++ b/sbroad-cartridge/src/init.lua
@@ -24,7 +24,11 @@ local function calculate_bucket_id(values, space_name) -- luacheck: no unused ar
         return nil, result
     end
 
-    return rust.calculate_bucket_id(values, space_name)
+    if space_name then
+        return rust.calculate_bucket_id(values, space_name)
+    end
+
+    return rust.calculate_bucket_id(values)
 end
 
 
diff --git a/sbroad-cartridge/src/utils.rs b/sbroad-cartridge/src/utils.rs
index c30c915f6eedb9e5d156b852b86e5c81e5992054..177fea558c031f5483c8048cab3db6b82e6d3faa 100644
--- a/sbroad-cartridge/src/utils.rs
+++ b/sbroad-cartridge/src/utils.rs
@@ -1,6 +1,7 @@
 use std::{fmt::Display, os::raw::c_int};
 
 use tarantool::{
+    error::IntoBoxError,
     proc::Return,
     tuple::{FunctionCtx, Tuple},
 };
@@ -41,7 +42,7 @@ impl Return for RawProcResult {
 #[derive(Debug, Clone)]
 pub struct RetResult<T, E>(pub Result<T, E>);
 
-impl<T: Return, E: Display> Return for RetResult<T, E> {
+impl<T: Return, E: Display + IntoBoxError> Return for RetResult<T, E> {
     fn ret(self, ctx: FunctionCtx) -> c_int {
         match self.0 {
             Ok(ok) => ok.ret(ctx),
diff --git a/sbroad-cartridge/test_app/test/data/config.yml b/sbroad-cartridge/test_app/test/data/config.yml
index d6dbe23269b2bf7d6084d8829559a71604608864..29934c14ed7062c43218dc995ff8de6f94251e13 100644
--- a/sbroad-cartridge/test_app/test/data/config.yml
+++ b/sbroad-cartridge/test_app/test/data/config.yml
@@ -1403,3 +1403,34 @@ schema:
       engine: memtx
       sharding_key:
         - id
+    datetime_t:
+      format:
+        - type: datetime
+          name: dt
+          is_nullable: false
+        - type: integer
+          name: a
+          is_nullable: false
+        - type: unsigned
+          name: bucket_id
+          is_nullable: false
+      temporary: false
+      indexes:
+        - unique: true
+          parts:
+            - path: dt
+              is_nullable: false
+              type: datetime
+          type: TREE
+          name: primary
+        - unique: false
+          parts:
+            - path: bucket_id
+              is_nullable: false
+              type: unsigned
+          type: TREE
+          name: bucket_id
+      is_local: false
+      engine: memtx
+      sharding_key:
+        - dt
diff --git a/sbroad-cartridge/test_app/test/integration/api_test.lua b/sbroad-cartridge/test_app/test/integration/api_test.lua
index 6e4ad12a8191e2c5c3955bfdd7f7b99bbb383196..9d39a905b2a4a578e9a7f8b95ff784655d821104 100644
--- a/sbroad-cartridge/test_app/test/integration/api_test.lua
+++ b/sbroad-cartridge/test_app/test/integration/api_test.lua
@@ -1,9 +1,8 @@
 local t = require('luatest')
-local datetime = require("datetime")
 local g = t.group('integration_api')
+local datetime = require('datetime')
 
 local helper = require('test.helper.cluster_no_replication')
-local config_handler = require('test.helper.config_handler')
 local cluster = nil
 
 g.before_all(function()
@@ -62,6 +61,17 @@ g.before_each(
         })
         t.assert_equals(err, nil)
         t.assert_equals(r, {row_count = 2})
+
+        -- "datetime_t" contains:
+        -- ['20.08.2021 +3', 1]
+        -- ['21.08.2021 +0', 1]
+        r, err = api:call("sbroad.execute", {
+            [[insert into "datetime_t" ("dt", "a") values (?, ?), (?, ?)]],
+            { datetime.new({day = 20,month = 8,year = 2021,tzoffset  = 180}), 1,
+            datetime.new({day = 21,month = 8,year = 2021,tzoffset  = 180}), 1}
+        })
+        t.assert_equals(err, nil)
+        t.assert_equals(r, {row_count = 2})
     end
 )
 
@@ -73,6 +83,7 @@ g.after_each(
         storage1:call("box.execute", { [[truncate table "space_simple_shard_key"]] })
         storage1:call("box.execute", { [[truncate table "space_simple_shard_key_hist"]] })
         storage1:call("box.execute", { [[truncate table "t"]] })
+        storage1:call("box.execute", { [[truncate table "datetime_t"]] })
 
         local storage2 = cluster:server("storage-2-1").net_box
         storage2:call("box.execute", { [[truncate table "testing_space"]] })
@@ -80,14 +91,10 @@ g.after_each(
         storage2:call("box.execute", { [[truncate table "space_simple_shard_key"]] })
         storage2:call("box.execute", { [[truncate table "space_simple_shard_key_hist"]] })
         storage2:call("box.execute", { [[truncate table "t"]] })
+        storage2:call("box.execute", { [[truncate table "datetime_t"]] })
     end
 )
 
-g.after_test("test_unsupported_column", function()
-    local default_config = config_handler.get_init_config(helper.root)
-    cluster:upload_config(default_config)
-end)
-
 g.after_all(function()
     helper.stop_test_cluster()
 end)
@@ -200,23 +207,6 @@ g.test_query_errored = function()
     local _, err = api:call("sbroad.execute", { [[SELECT "NotFoundColumn" FROM "testing_space"]], {} })
     t.assert_equals(tostring(err), "Sbroad Error: build query: column with name \"NotFoundColumn\" not found")
 
-    local invalid_type_param = datetime.new{
-        nsec = 123456789,
-        sec = 20,
-        min = 25,
-        hour = 18,
-        day = 20,
-        month = 8,
-        year = 2021,
-        tzoffset  = 180
-    }
-
-    local _, err = api:call("sbroad.execute", { [[SELECT * FROM "testing_space" where "id" = ?]], {invalid_type_param} })
-    t.assert_str_contains(
-        tostring(err),
-        "Sbroad Error: build params: pattern with parameters parsing error"
-    )
-
     -- check err when params lenght is less then amount of sign `?`
     local _, err = api:call("sbroad.execute", { [[SELECT * FROM "testing_space" where "id" = ?]], {} })
     t.assert_equals(
@@ -225,46 +215,6 @@ g.test_query_errored = function()
     )
 end
 
-g.test_unsupported_column = function()
-    local api = cluster:server("api-1").net_box
-
-    local config = cluster:download_config()
-    local space_with_unsupported_column = {
-        format = {
-            { type = "integer", name = "id", is_nullable = false },
-            { type = "datetime", name = "unsupported_column", is_nullable = false },
-            { type = "unsigned", name = "bucket_id", is_nullable = true },
-        },
-        temporary = false,
-        engine = "memtx",
-        is_local = false,
-        sharding_key = { "id" },
-        indexes = {
-            {
-                unique = true,
-                parts = {{ path = "id", type = "integer", is_nullable = false}},
-                name = "id",
-                type = "TREE"
-            },
-            {
-                unique = false,
-                parts = { { path = "bucket_id", type = "unsigned", is_nullable = true } },
-                name = "bucket_id",
-                type = "TREE"
-            }
-        }
-    }
-
-    config["schema"]["spaces"]["space_with_unsupported_column"] = space_with_unsupported_column
-    cluster:upload_config(config)
-
-    local _, err = api:call("sbroad.execute", { [[SELECT * FROM "space_with_unsupported_column"]], {} })
-    t.assert_str_contains(
-        tostring(err),
-        "type datetime not implemented"
-    )
-end
-
 g.test_join_query_is_valid = function()
     local api = cluster:server("api-1").net_box
 
@@ -527,3 +477,79 @@ g.test_pg_style_params2 = function()
         },
     })
 end
+
+g.test_datetime_select = function ()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", { [[
+        SELECT * from "datetime_t"
+    ]]})
+
+    t.assert_equals(err, nil)
+    local expected_rows = {
+        { datetime.new({day = 20,month = 8,year = 2021,tzoffset = 180}), 1},
+        { datetime.new({day = 21,month = 8,year = 2021,tzoffset = 180}), 1},
+    }
+    t.assert_equals(r.metadata, {
+        {name = "dt", type = "datetime"},
+        {name = "a", type = "integer"},
+    })
+    t.assert_items_equals(r.rows, expected_rows)
+end
+
+g.test_datetime_insert = function ()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", { [[
+        insert into "datetime_t" select to_date(COLUMN_1, '%c'),
+        cast(COLUMN_2 as int) from
+        (values ('Thu Jan  1 03:44:00 1970', 100))
+    ]]})
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {row_count = 1})
+
+    r, err = api:call("sbroad.execute", { [[
+        SELECT * from "datetime_t"
+        where "a" = 100
+    ]]})
+
+    t.assert_equals(err, nil)
+    local expected_rows = {
+        { datetime.new({day = 1,month = 1,year = 1970}), 100},
+    }
+    t.assert_equals(r.metadata, {
+        {name = "dt", type = "datetime"},
+        {name = "a", type = "integer"},
+    })
+    t.assert_items_equals(r.rows, expected_rows)
+end
+
+g.after_test('test_datetime_insert', function ()
+    local api = cluster:server("api-1").net_box
+
+    local r, err = api:call("sbroad.execute", { [[
+        delete from "datetime_t" where "a" = 100
+    ]]})
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {row_count = 1})
+end)
+
+g.test_datetime_motion = function ()
+    local api = cluster:server("api-1").net_box
+
+    -- inner table for such condition is always
+    -- broadcasted to all other nodes
+    local r, err = api:call("sbroad.execute", { [[
+        select "a" as "u" from (select "id" from "testing_space") join "datetime_t"
+        on true
+    ]]})
+
+    t.assert_equals(err, nil)
+    t.assert_equals(r, {
+        metadata = {
+            {name = "u", type = "integer"},
+        },
+        rows = { {1}, {1} },
+    })
+end
+
diff --git a/sbroad-core/src/backend/sql/ir/tests/selection.rs b/sbroad-core/src/backend/sql/ir/tests/selection.rs
index 06a54ef5848cda75bb79f20c8ef60b2185e6f56c..889ecbba285211f44b4e6f6d99031e8d9f46aadd 100644
--- a/sbroad-core/src/backend/sql/ir/tests/selection.rs
+++ b/sbroad-core/src/backend/sql/ir/tests/selection.rs
@@ -67,9 +67,9 @@ fn selection2_latest() {
     let expected = PatternWithParams::new(
         [
             r#"SELECT "hash_testing"."product_code" FROM "hash_testing""#,
-            r#"WHERE ("hash_testing"."identification_number", "hash_testing"."product_units", "hash_testing"."identification_number") = ("hash_testing"."product_units", ?, ?)"#,
+            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"."identification_number", "hash_testing"."product_units", "hash_testing"."identification_number") = ("hash_testing"."product_units", ?, ?)"#,
+            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)],
diff --git a/sbroad-core/src/builtins.lua b/sbroad-core/src/builtins.lua
new file mode 100644
index 0000000000000000000000000000000000000000..48dbd671d5ab35d432e62c209c5fdd5738599ae1
--- /dev/null
+++ b/sbroad-core/src/builtins.lua
@@ -0,0 +1,52 @@
+local dt = require('datetime')
+local helper = require('sbroad.helper')
+
+-- Builtin sbroad funcs inplemented in LUA
+local builtins = {}
+
+builtins.TO_DATE = function (s, fmt)
+    local opts = {}
+    if fmt and fmt ~= '' then
+      opts = { format = fmt }
+    end
+    -- ignore the second returned value
+    local res = dt.parse(s, opts)
+    -- we must return only date part
+    res:set({hour=0, min=0, sec=0, nsec=0})
+    return res
+end
+
+local function init()
+  -- cartridge
+  local module = 'sbroad'
+  if helper.pico_compat() then
+    -- picodata
+    module = 'pico'
+  end
+
+  if rawget(_G, module) == nil then
+    error('buitins must be initialized after app module was set!')
+  end
+
+  _G[module].builtins = builtins
+
+  -- We don't want to make builtin functions global,
+  -- so we put them in module. But for function
+  -- to be callable from sql it should have a simple
+  -- name, so for each func we provide a body.
+  local body = string.format("function(...) return %s.builtins.TO_DATE(...) end",
+  module)
+  box.schema.func.create("TO_DATE", {
+      language = 'LUA',
+      returns = 'datetime',
+      body = body,
+      param_list = {'string', 'string'},
+      exports = {'SQL'},
+      is_deterministic = true,
+      if_not_exists=true
+  })
+end
+
+return {
+  init = init,
+}
diff --git a/sbroad-core/src/cbo/selectivity.rs b/sbroad-core/src/cbo/selectivity.rs
index 7e36df176d006fc7bc2a240aa89970e6131694e3..f1a162d298da8a61c2351cad16c02a871ae44daf 100644
--- a/sbroad-core/src/cbo/selectivity.rs
+++ b/sbroad-core/src/cbo/selectivity.rs
@@ -333,7 +333,7 @@ pub fn calculate_filter_selectivity(
         Type::Scalar => {
             todo!("Don't know what to do here")
         }
-        Type::Array | Type::Any | Type::Map => Err(SbroadError::Invalid(
+        Type::Array | Type::Any | Type::Map | Type::Datetime => Err(SbroadError::Invalid(
             Entity::Statistics,
             Some(SmolStr::from(
                 "Unable to calculate selectivity for array type column",
diff --git a/sbroad-core/src/core-storage.lua b/sbroad-core/src/core-storage.lua
index bff371f9eb1874d74954268c8af86f0d2fff31d9..3b6e4fae6ee84fc9c0ff22d5ce106c00a0f1e2dd 100644
--- a/sbroad-core/src/core-storage.lua
+++ b/sbroad-core/src/core-storage.lua
@@ -66,3 +66,4 @@ _G.write = function(stmt_id, stmt, params, options)
 
     return box.tuple.new{res}
 end
+
diff --git a/sbroad-core/src/executor/engine.rs b/sbroad-core/src/executor/engine.rs
index 8663c0bceb98e15aa055af983f140c25fcc48501..518f2185f4073bffc830c7d6034b95c958af841e 100644
--- a/sbroad-core/src/executor/engine.rs
+++ b/sbroad-core/src/executor/engine.rs
@@ -10,6 +10,7 @@ use std::any::Any;
 use std::cell::{Ref, RefCell};
 use std::collections::HashMap;
 use std::rc::Rc;
+use std::sync::OnceLock;
 
 use crate::errors::SbroadError;
 use crate::executor::bucket::Buckets;
@@ -19,6 +20,7 @@ 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::value::Value;
 
 use super::ir::{ConnectionType, QueryType};
@@ -65,6 +67,20 @@ pub trait Metadata: Sized {
     fn sharding_positions_by_space(&self, space: &str) -> Result<Vec<usize>, SbroadError>;
 }
 
+pub fn get_builtin_functions() -> &'static [Function] {
+    // Once lock is used because of concurrent access in tests.
+    static mut BUILTINS: OnceLock<Vec<Function>> = OnceLock::new();
+
+    unsafe {
+        BUILTINS.get_or_init(|| {
+            vec![
+                Function::new_stable("\"TO_DATE\"".into(), Type::Datetime),
+                Function::new_stable("\"TRIM\"".into(), Type::String),
+            ]
+        })
+    }
+}
+
 pub trait StorageCache {
     /// Put the prepared statement with given key in cache,
     /// remembering its version.
diff --git a/sbroad-core/src/executor/engine/mock.rs b/sbroad-core/src/executor/engine/mock.rs
index bb5708bb06bdb23b57c6aacbc0e692f967a9dc8e..c77df93e27138a94dde8678eca080b74be95c8a8 100644
--- a/sbroad-core/src/executor/engine/mock.rs
+++ b/sbroad-core/src/executor/engine/mock.rs
@@ -39,7 +39,7 @@ use crate::ir::Plan;
 
 use super::helpers::vshard::{prepare_rs_to_ir_map, GroupedBuckets};
 use super::helpers::{dispatch_by_buckets, normalize_name_from_sql};
-use super::{Metadata, QueryCache};
+use super::{get_builtin_functions, Metadata, QueryCache};
 
 #[allow(clippy::module_name_repetitions)]
 #[derive(Debug, Clone)]
@@ -110,6 +110,9 @@ impl RouterConfigurationMock {
         let mut functions = HashMap::new();
         functions.insert(name_func, fn_func);
         functions.insert(name_trim, trim_func);
+        for f in get_builtin_functions() {
+            functions.insert(f.name.clone(), f.clone());
+        }
 
         let mut tables = HashMap::new();
 
diff --git a/sbroad-core/src/executor/protocol.rs b/sbroad-core/src/executor/protocol.rs
index de3aae95eabe8421a22d3727bda5d8cfbe149ea6..b7f5d3060bc5ad956ef7222248f7a530a9434dbb 100644
--- a/sbroad-core/src/executor/protocol.rs
+++ b/sbroad-core/src/executor/protocol.rs
@@ -222,7 +222,7 @@ impl TryFrom<OptionalData> for Vec<u8> {
         bincode::serialize(&value).map_err(|e| {
             SbroadError::FailedTo(
                 Action::Serialize,
-                Some(Entity::RequiredData),
+                Some(Entity::OptionalData),
                 format_smolstr!("to binary: {e:?}"),
             )
         })
@@ -236,7 +236,7 @@ impl TryFrom<&[u8]> for OptionalData {
         bincode::deserialize(value).map_err(|e| {
             SbroadError::FailedTo(
                 Action::Deserialize,
-                Some(Entity::RequiredData),
+                Some(Entity::OptionalData),
                 format_smolstr!("{e:?}"),
             )
         })
diff --git a/sbroad-core/src/executor/result.rs b/sbroad-core/src/executor/result.rs
index b2c02b6f82d7e4ecd150c6f7ad224cd0f7d98c44..00fd6404bc0c6e0e8f424c09670d3237bf6020a3 100644
--- a/sbroad-core/src/executor/result.rs
+++ b/sbroad-core/src/executor/result.rs
@@ -25,7 +25,7 @@ use crate::ir::tree::traversal::{PostOrderWithFilter, REL_CAPACITY};
 use crate::ir::value::{LuaValue, Value};
 use crate::ir::{Node, Plan};
 
-type ExecutorTuple = Vec<LuaValue>;
+pub type ExecutorTuple = Vec<LuaValue>;
 
 #[derive(LuaRead, Debug, Deserialize, PartialEq, Eq, Clone)]
 pub struct MetadataColumn {
@@ -63,6 +63,12 @@ impl TryInto<Column> for &MetadataColumn {
                 ColumnRole::User,
                 true,
             )),
+            "datetime" => Ok(Column::new(
+                &self.name,
+                Type::Datetime,
+                ColumnRole::User,
+                true,
+            )),
             "decimal" => Ok(Column::new(
                 &self.name,
                 Type::Decimal,
diff --git a/sbroad-core/src/executor/tests/exec_plan.rs b/sbroad-core/src/executor/tests/exec_plan.rs
index d6e92e208a47a7a89e6291cfdb0c53b8923ea423..bc6b6d96aa1a7461ac1848d3e96e6b931128b4c3 100644
--- a/sbroad-core/src/executor/tests/exec_plan.rs
+++ b/sbroad-core/src/executor/tests/exec_plan.rs
@@ -298,8 +298,8 @@ fn exec_plan_subtree_aggregates() {
             format!(
                 "{} {} {} {} {} {}",
                 r#"SELECT "T1"."sys_op" as "column_12", ("T1"."id") * ("T1"."sys_op") as "column_49","#,
-                r#""T1"."id" as "column_46", group_concat ("T1"."id", ?) as "group_concat_58","#,
-                r#"sum ("T1"."id") as "sum_42", count ("T1"."id") as "count_61", total ("T1"."id") as "total_64","#,
+                r#""T1"."id" as "column_46", total ("T1"."id") as "total_64","#,
+                r#"sum ("T1"."id") as "sum_42", count ("T1"."id") as "count_61", group_concat ("T1"."id", ?) as "group_concat_58","#,
                 r#"min ("T1"."id") as "min_67", max ("T1"."id") as "max_70", count ("T1"."sysFrom") as "count_37""#,
                 r#"FROM "test_space" as "T1""#,
                 r#"GROUP BY "T1"."sys_op", ("T1"."id") * ("T1"."sys_op"), "T1"."id""#,
diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs
index 1fa642d8920aecd9e79b36b4a63eb63714018194..e2e6222be19a54d97716d3cae70ca2440c709c0b 100644
--- a/sbroad-core/src/frontend/sql.rs
+++ b/sbroad-core/src/frontend/sql.rs
@@ -522,6 +522,9 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
                                     Rule::TypeBool => {
                                         column_def.data_type = RelationType::Boolean;
                                     }
+                                    Rule::TypeDatetime => {
+                                        column_def.data_type = RelationType::Datetime;
+                                    }
                                     Rule::TypeDecimal => {
                                         column_def.data_type = RelationType::Decimal;
                                     }
@@ -675,8 +678,9 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
                             for shard_col_id in &shard_node.children {
                                 let shard_col_name = parse_identifier(ast, *shard_col_id)?;
 
-                                let column_found = columns.iter().any(|c| c.name == shard_col_name);
-                                if !column_found {
+                                let column_found =
+                                    columns.iter().find(|c| c.name == shard_col_name);
+                                if column_found.is_none() {
                                     return Err(SbroadError::Invalid(
                                         Entity::Column,
                                         Some(format_smolstr!(
@@ -685,6 +689,17 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl,
                                     ));
                                 }
 
+                                if let Some(column) = column_found {
+                                    if !column.data_type.is_scalar() {
+                                        return Err(SbroadError::Invalid(
+                                            Entity::Column,
+                                            Some(format_smolstr!(
+                                                "Sharding key column {shard_col_name} is not of scalar type."
+                                            )),
+                                        ));
+                                    }
+                                }
+
                                 shard_key.push(shard_col_name);
                             }
                         }
diff --git a/sbroad-core/src/frontend/sql/ir/tests.rs b/sbroad-core/src/frontend/sql/ir/tests.rs
index 3b518023f7a0f02bef88872dceb3f4020e188729..c52565cb32cf1417a12d5c9cdd1d2199dcb1764f 100644
--- a/sbroad-core/src/frontend/sql/ir/tests.rs
+++ b/sbroad-core/src/frontend/sql/ir/tests.rs
@@ -3214,6 +3214,24 @@ vtable_max_rows = 5000
     )
 }
 
+#[test]
+fn front_sql_to_date() {
+    assert_explain_eq(
+        r#"
+            SELECT to_date(COLUMN_1, '%Y/%d/%m') FROM (values ('2010/10/10'))
+        "#,
+        vec![],
+        r#"projection ("TO_DATE"(("COLUMN_1"::string, '%Y/%d/%m'::string))::datetime -> "COL_1")
+    scan
+        values
+            value row (data=ROW('2010/10/10'::string))
+execution options:
+sql_vdbe_max_steps = 45000
+vtable_max_rows = 5000
+"#,
+    )
+}
+
 #[test]
 fn front_sql_check_non_null_columns_specified() {
     let input = r#"insert into "test_space" ("sys_op") values (1)"#;
diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest
index bf471acdef31d6cece5b8b10bf30722d3c07b40e..6344906a3c7450a15b481caa4c6768317a330c38 100644
--- a/sbroad-core/src/frontend/sql/query.pest
+++ b/sbroad-core/src/frontend/sql/query.pest
@@ -259,10 +259,11 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* }
                 TrimTarget = { Expr }
             Cast = { ^"cast" ~ "(" ~ Expr ~ ^"as" ~ TypeCast ~ ")" }
                 TypeCast = _{ TypeAny | ColumnDefType }
-                ColumnDefType = { TypeBool | TypeDecimal | TypeDouble | TypeInt | TypeNumber
+                ColumnDefType = { TypeBool | TypeDatetime | TypeDecimal | TypeDouble | TypeInt | TypeNumber
                                    | TypeScalar | TypeString | TypeText | TypeUnsigned | TypeVarchar | TypeUuid }
                     TypeAny = { ^"any" }
                     TypeBool = { (^"boolean" | ^"bool") }
+                    TypeDatetime = { ^"datetime" }
                     TypeDecimal = { ^"decimal" }
                     TypeDouble = { ^"double" }
                     TypeInt = { (^"integer" | ^"int") }
diff --git a/sbroad-core/src/ir/expression/cast.rs b/sbroad-core/src/ir/expression/cast.rs
index 94e6a40de5344c5454ffffdc30e6cadfab10068e..76835af9a4054215fbb9ae3d558572ed8a798a47 100644
--- a/sbroad-core/src/ir/expression/cast.rs
+++ b/sbroad-core/src/ir/expression/cast.rs
@@ -13,6 +13,7 @@ pub enum Type {
     Any,
     Map,
     Boolean,
+    Datetime,
     Decimal,
     Double,
     Integer,
@@ -58,6 +59,7 @@ impl TryFrom<&RelationType> for Type {
     fn try_from(relational_type: &RelationType) -> Result<Self, Self::Error> {
         match relational_type {
             RelationType::Boolean => Ok(Type::Boolean),
+            RelationType::Datetime => Ok(Type::Datetime),
             RelationType::Decimal => Ok(Type::Decimal),
             RelationType::Double => Ok(Type::Double),
             RelationType::Integer => Ok(Type::Integer),
@@ -82,6 +84,7 @@ impl From<&Type> for SmolStr {
             Type::Any => "any".to_smolstr(),
             Type::Map => "map".to_smolstr(),
             Type::Boolean => "bool".to_smolstr(),
+            Type::Datetime => "datetime".to_smolstr(),
             Type::Decimal => "decimal".to_smolstr(),
             Type::Double => "double".to_smolstr(),
             Type::Integer => "int".to_smolstr(),
@@ -109,6 +112,7 @@ impl Type {
             Type::Any | Type::Scalar => RelationType::Scalar,
             Type::Map => RelationType::Map,
             Type::Boolean => RelationType::Boolean,
+            Type::Datetime => RelationType::Datetime,
             Type::Decimal => RelationType::Decimal,
             Type::Double => RelationType::Double,
             Type::Integer => RelationType::Integer,
diff --git a/sbroad-core/src/ir/relation.rs b/sbroad-core/src/ir/relation.rs
index b92cd3f7a4bfa8831405f4d91a7ff31b99b48d19..f86f41db719cb3ff65a00402b0c9b3444eb6ad4e 100644
--- a/sbroad-core/src/ir/relation.rs
+++ b/sbroad-core/src/ir/relation.rs
@@ -36,6 +36,7 @@ pub enum Type {
     Map,
     Array,
     Boolean,
+    Datetime,
     Decimal,
     Double,
     Integer,
@@ -53,6 +54,7 @@ impl fmt::Display for Type {
             Type::Array => write!(f, "array"),
             Type::Boolean => write!(f, "boolean"),
             Type::Decimal => write!(f, "decimal"),
+            Type::Datetime => write!(f, "datetime"),
             Type::Double => write!(f, "double"),
             Type::Integer => write!(f, "integer"),
             Type::Scalar => write!(f, "scalar"),
@@ -71,6 +73,7 @@ impl From<&Type> for FieldType {
         match data_type {
             Type::Boolean => FieldType::Boolean,
             Type::Decimal => FieldType::Decimal,
+            Type::Datetime => FieldType::Datetime,
             Type::Double => FieldType::Double,
             Type::Integer => FieldType::Integer,
             Type::Number => FieldType::Number,
@@ -89,6 +92,7 @@ impl From<&Type> for SpaceFieldType {
     fn from(data_type: &Type) -> Self {
         match data_type {
             Type::Boolean => SpaceFieldType::Boolean,
+            Type::Datetime => SpaceFieldType::Datetime,
             Type::Decimal => SpaceFieldType::Decimal,
             Type::Double => SpaceFieldType::Double,
             Type::Integer => SpaceFieldType::Integer,
@@ -110,6 +114,7 @@ impl TryFrom<SpaceFieldType> for Type {
     fn try_from(field_type: SpaceFieldType) -> Result<Self, Self::Error> {
         match field_type {
             SpaceFieldType::Boolean => Ok(Type::Boolean),
+            SpaceFieldType::Datetime => Ok(Type::Datetime),
             SpaceFieldType::Decimal => Ok(Type::Decimal),
             SpaceFieldType::Double => Ok(Type::Double),
             SpaceFieldType::Integer => Ok(Type::Integer),
@@ -122,8 +127,7 @@ impl TryFrom<SpaceFieldType> for Type {
             SpaceFieldType::Any
             | SpaceFieldType::Varbinary
             | SpaceFieldType::Map
-            | SpaceFieldType::Interval
-            | SpaceFieldType::Datetime => Err(SbroadError::NotImplemented(
+            | SpaceFieldType::Interval => Err(SbroadError::NotImplemented(
                 Entity::Type,
                 field_type.to_smolstr(),
             )),
@@ -141,6 +145,7 @@ impl Type {
     pub fn new(s: &str) -> Result<Self, SbroadError> {
         match s.to_string().to_lowercase().as_str() {
             "boolean" => Ok(Type::Boolean),
+            "datetime" => Ok(Type::Datetime),
             "decimal" => Ok(Type::Decimal),
             "double" => Ok(Type::Double),
             "integer" => Ok(Type::Integer),
@@ -167,6 +172,7 @@ impl Type {
             | "string" | "uuid" | "text" | "unsigned" => Ok(Type::Scalar),
             "array" => Ok(Type::Array),
             "map" => Ok(Type::Map),
+            "datetime" => Ok(Type::Datetime),
             "any" => Ok(Type::Any),
             v => Err(SbroadError::Invalid(
                 Entity::Type,
@@ -182,6 +188,7 @@ impl Type {
         matches!(
             self,
             Type::Boolean
+                | Type::Datetime
                 | Type::Decimal
                 | Type::Double
                 | Type::Integer
@@ -249,6 +256,7 @@ 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),
@@ -288,6 +296,7 @@ impl SerSerialize for Column {
         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")?,
@@ -348,6 +357,7 @@ impl<'de> Visitor<'de> for ColumnVisitor {
 
         match column_type.as_str() {
             "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)),
@@ -359,7 +369,7 @@ impl<'de> Visitor<'de> for ColumnVisitor {
             "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)),
-            _ => Err(Error::custom("unsupported column type")),
+            s => Err(Error::custom(format!("unsupported column type: {s}"))),
         }
     }
 }
diff --git a/sbroad-core/src/ir/transformation/equality_propagation/tests.rs b/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
index f1cf25a1678935bdccdefee136bef8fc4f41b85a..ba9182fe205c7db390183bb59b2f199bbacecb61 100644
--- a/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
+++ b/sbroad-core/src/ir/transformation/equality_propagation/tests.rs
@@ -80,7 +80,7 @@ fn equality_propagation4() {
             "{} {} {}",
             r#"SELECT "t"."a" FROM "t""#,
             r#"WHERE ("t"."b") = (?) and ("t"."a") = (?) and ("t"."a") = (?)"#,
-            r#"and ("t"."b") = (?) and ("t"."b") = ("t"."a")"#,
+            r#"and ("t"."b") = (?) and ("t"."a") = ("t"."b")"#,
         ),
         vec![
             Value::from(1_u64),
@@ -107,8 +107,8 @@ fn equality_propagation5() {
             r#"SELECT "t"."a" FROM "t""#,
             r#"WHERE ("t"."d") = (?) and ("t"."c") = (?)"#,
             r#"and ("t"."a") = (?) and ("t"."b") = (?)"#,
-            r#"and ("t"."c") = ("t"."d") and ("t"."d") = ("t"."b")"#,
-            r#"and ("t"."b") = ("t"."a")"#,
+            r#"and ("t"."c") = ("t"."a") and ("t"."a") = ("t"."d")"#,
+            r#"and ("t"."d") = ("t"."b")"#,
         ),
         vec![
             Value::from(1_u64),
diff --git a/sbroad-core/src/ir/value.rs b/sbroad-core/src/ir/value.rs
index 869ae1222c864b0eff93fc6ae4a783b5e3da1d94..49724294dbea1d35338d9ed5200f733f33a51a9d 100644
--- a/sbroad-core/src/ir/value.rs
+++ b/sbroad-core/src/ir/value.rs
@@ -8,6 +8,7 @@ use std::str::FromStr;
 
 use serde::{Deserialize, Serialize};
 use smol_str::{format_smolstr, SmolStr, ToSmolStr};
+use tarantool::datetime::Datetime;
 use tarantool::decimal::Decimal;
 use tarantool::tlua::{self, LuaRead};
 use tarantool::tuple::{FieldType, KeyDefPart};
@@ -121,6 +122,8 @@ pub enum Value {
     Decimal(Decimal),
     /// Floating point type.
     Double(Double),
+    /// Datetime type,
+    Datetime(Datetime),
     /// Signed integer type.
     Integer(i64),
     /// SQL NULL ("unknown" in the terms of three-valued logic).
@@ -185,6 +188,7 @@ impl fmt::Display for Value {
             Value::Null => write!(f, "NULL"),
             Value::Unsigned(v) => write!(f, "{v}"),
             Value::Integer(v) => write!(f, "{v}"),
+            Value::Datetime(v) => write!(f, "{v}"),
             Value::Double(v) => fmt::Display::fmt(&v, f),
             Value::Decimal(v) => fmt::Display::fmt(v, f),
             Value::String(v) => write!(f, "'{v}'"),
@@ -445,6 +449,7 @@ impl Value {
 
     /// Checks equality of the two values.
     /// The result uses three-valued logic.
+    #[allow(clippy::too_many_lines)]
     #[must_use]
     pub fn eq(&self, other: &Value) -> Trivalent {
         match self {
@@ -453,6 +458,7 @@ impl Value {
                 Value::Null => Trivalent::Unknown,
                 Value::Unsigned(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::String(_)
@@ -461,9 +467,11 @@ impl Value {
             },
             Value::Null => Trivalent::Unknown,
             Value::Integer(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => {
-                    Trivalent::False
-                }
+                Value::Boolean(_)
+                | Value::String(_)
+                | Value::Uuid(_)
+                | Value::Tuple(_)
+                | Value::Datetime(_) => Trivalent::False,
                 Value::Null => Trivalent::Unknown,
                 Value::Integer(o) => (s == o).into(),
                 Value::Decimal(o) => (&Decimal::from(*s) == o).into(),
@@ -474,9 +482,11 @@ impl Value {
                 Value::Unsigned(o) => (&Decimal::from(*s) == o).into(),
             },
             Value::Double(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) | Value::Uuid(_) => {
-                    Trivalent::False
-                }
+                Value::Boolean(_)
+                | Value::String(_)
+                | Value::Tuple(_)
+                | Value::Uuid(_)
+                | Value::Datetime(_) => Trivalent::False,
                 Value::Null => Trivalent::Unknown,
                 Value::Integer(o) => (*s == Double::from(*o)).into(),
                 // If double can't be converted to decimal without error then it is not equal to decimal.
@@ -488,9 +498,11 @@ impl Value {
                 }
             },
             Value::Decimal(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) | Value::Uuid(_) => {
-                    Trivalent::False
-                }
+                Value::Boolean(_)
+                | Value::String(_)
+                | Value::Tuple(_)
+                | Value::Uuid(_)
+                | Value::Datetime(_) => Trivalent::False,
                 Value::Null => Trivalent::Unknown,
                 Value::Integer(o) => (s == &Decimal::from(*o)).into(),
                 Value::Decimal(o) => (s == o).into(),
@@ -499,9 +511,11 @@ impl Value {
                 Value::Unsigned(o) => (s == &Decimal::from(*o)).into(),
             },
             Value::Unsigned(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => {
-                    Trivalent::False
-                }
+                Value::Boolean(_)
+                | Value::String(_)
+                | Value::Uuid(_)
+                | Value::Tuple(_)
+                | Value::Datetime(_) => Trivalent::False,
                 Value::Null => Trivalent::Unknown,
                 Value::Integer(o) => (Decimal::from(*s) == *o).into(),
                 Value::Decimal(o) => (&Decimal::from(*s) == o).into(),
@@ -514,6 +528,7 @@ impl Value {
             Value::String(s) => match other {
                 Value::Boolean(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::Unsigned(_)
@@ -525,6 +540,7 @@ impl Value {
             Value::Tuple(_) => match other {
                 Value::Boolean(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::Unsigned(_)
@@ -536,6 +552,7 @@ impl Value {
             Value::Uuid(s) => match other {
                 Value::Boolean(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::String(_)
@@ -544,6 +561,18 @@ impl Value {
                 Value::Null => Trivalent::Unknown,
                 Value::Uuid(o) => s.eq(o).into(),
             },
+            Value::Datetime(s) => match other {
+                Value::Boolean(_)
+                | Value::Integer(_)
+                | Value::String(_)
+                | Value::Decimal(_)
+                | Value::Double(_)
+                | Value::Unsigned(_)
+                | Value::Uuid(_)
+                | Value::Tuple(_) => Trivalent::False,
+                Value::Null => Trivalent::Unknown,
+                Value::Datetime(o) => s.eq(o).into(),
+            },
         }
     }
 
@@ -552,6 +581,7 @@ impl Value {
         let field_type = match self {
             Value::Boolean(_) => FieldType::Boolean,
             Value::Integer(_) => FieldType::Integer,
+            Value::Datetime(_) => FieldType::Datetime,
             Value::Decimal(_) => FieldType::Decimal,
             Value::Double(_) => FieldType::Double,
             Value::Unsigned(_) => FieldType::Unsigned,
@@ -585,6 +615,7 @@ impl Value {
                 Value::Null => TrivalentOrdering::Unknown.into(),
                 Value::Unsigned(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::String(_)
@@ -593,7 +624,11 @@ impl Value {
             },
             Value::Null => TrivalentOrdering::Unknown.into(),
             Value::Integer(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => None,
+                Value::Boolean(_)
+                | Value::Datetime(_)
+                | Value::String(_)
+                | Value::Uuid(_)
+                | Value::Tuple(_) => None,
                 Value::Null => TrivalentOrdering::Unknown.into(),
                 Value::Integer(o) => TrivalentOrdering::from(s.cmp(o)).into(),
                 Value::Decimal(o) => TrivalentOrdering::from(Decimal::from(*s).cmp(o)).into(),
@@ -610,8 +645,24 @@ impl Value {
                     TrivalentOrdering::from(Decimal::from(*s).cmp(&Decimal::from(*o))).into()
                 }
             },
+            Value::Datetime(s) => match other {
+                Value::Boolean(_)
+                | Value::Integer(_)
+                | Value::Decimal(_)
+                | Value::Double(_)
+                | Value::Unsigned(_)
+                | Value::Uuid(_)
+                | Value::String(_)
+                | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::Datetime(o) => TrivalentOrdering::from(s.cmp(o)).into(),
+            },
             Value::Double(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) | Value::Uuid(_) => None,
+                Value::Boolean(_)
+                | Value::Datetime(_)
+                | Value::String(_)
+                | Value::Tuple(_)
+                | Value::Uuid(_) => None,
                 Value::Null => TrivalentOrdering::Unknown.into(),
                 Value::Integer(o) => {
                     if let Some(ord) = s.partial_cmp(&Double::from(*o)) {
@@ -645,7 +696,11 @@ impl Value {
                 }
             },
             Value::Decimal(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => None,
+                Value::Boolean(_)
+                | Value::Datetime(_)
+                | Value::String(_)
+                | Value::Uuid(_)
+                | Value::Tuple(_) => None,
                 Value::Null => TrivalentOrdering::Unknown.into(),
                 Value::Integer(o) => TrivalentOrdering::from(s.cmp(&Decimal::from(*o))).into(),
                 Value::Decimal(o) => TrivalentOrdering::from(s.cmp(o)).into(),
@@ -660,7 +715,11 @@ impl Value {
                 Value::Unsigned(o) => TrivalentOrdering::from(s.cmp(&Decimal::from(*o))).into(),
             },
             Value::Unsigned(s) => match other {
-                Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => None,
+                Value::Boolean(_)
+                | Value::Datetime(_)
+                | Value::String(_)
+                | Value::Uuid(_)
+                | Value::Tuple(_) => None,
                 Value::Null => TrivalentOrdering::Unknown.into(),
                 Value::Integer(o) => {
                     TrivalentOrdering::from(Decimal::from(*s).cmp(&Decimal::from(*o))).into()
@@ -679,6 +738,7 @@ impl Value {
             Value::String(s) => match other {
                 Value::Boolean(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::Unsigned(_)
@@ -690,6 +750,7 @@ impl Value {
             Value::Uuid(u) => match other {
                 Value::Boolean(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::Unsigned(_)
@@ -701,6 +762,7 @@ impl Value {
             Value::Tuple(_) => match other {
                 Value::Boolean(_)
                 | Value::Integer(_)
+                | Value::Datetime(_)
                 | Value::Decimal(_)
                 | Value::Double(_)
                 | Value::Unsigned(_)
@@ -747,6 +809,15 @@ impl Value {
                     format_smolstr!("{self:?} into boolean"),
                 )),
             },
+            Type::Datetime => match self {
+                Value::Null => Ok(Value::Null.into()),
+                Value::Datetime(_) => Ok(self.into()),
+                _ => Err(SbroadError::FailedTo(
+                    Action::Serialize,
+                    Some(Entity::Value),
+                    format_smolstr!("{self:?} into datetime"),
+                )),
+            },
             Type::Decimal => match self {
                 Value::Decimal(_) => Ok(self.into()),
                 Value::Double(v) => Ok(Value::Decimal(
@@ -907,6 +978,7 @@ impl Value {
         match self {
             Value::Unsigned(_) => Type::Unsigned,
             Value::Integer(_) => Type::Integer,
+            Value::Datetime(_) => Type::Datetime,
             Value::Decimal(_) => Type::Decimal,
             Value::Double(_) => Type::Double,
             Value::Boolean(_) => Type::Boolean,
@@ -923,6 +995,7 @@ impl ToHashString for Value {
         match self {
             Value::Unsigned(v) => v.to_string(),
             Value::Integer(v) => v.to_string(),
+            Value::Datetime(v) => v.to_string(),
             // It is important to trim trailing zeros when converting to string.
             // Otherwise, the hash from `1.000` and `1` would be different,
             // though the values are the same.
@@ -977,6 +1050,7 @@ impl From<Value> for EncodedValue<'_> {
 #[serde(untagged)]
 pub enum MsgPackValue<'v> {
     Boolean(&'v bool),
+    Datetime(&'v Datetime),
     Decimal(&'v Decimal),
     Double(&'v f64),
     Integer(&'v i64),
@@ -991,6 +1065,7 @@ impl<'v> From<&'v Value> for MsgPackValue<'v> {
     fn from(value: &'v Value) -> Self {
         match value {
             Value::Boolean(v) => MsgPackValue::Boolean(v),
+            Value::Datetime(v) => MsgPackValue::Datetime(v),
             Value::Decimal(v) => MsgPackValue::Decimal(v),
             Value::Double(v) => MsgPackValue::Double(&v.value),
             Value::Integer(v) => MsgPackValue::Integer(v),
@@ -1008,6 +1083,7 @@ impl<'v> From<&'v Value> for MsgPackValue<'v> {
 #[serde(untagged)]
 pub enum LuaValue {
     Boolean(bool),
+    Datetime(Datetime),
     Decimal(Decimal),
     Double(f64),
     Integer(i64),
@@ -1022,6 +1098,7 @@ impl fmt::Display for LuaValue {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
             LuaValue::Boolean(v) => write!(f, "{v}"),
+            LuaValue::Datetime(v) => write!(f, "{v}"),
             LuaValue::Decimal(v) => fmt::Display::fmt(v, f),
             LuaValue::Double(v) => write!(f, "{v}"),
             LuaValue::Integer(v) => write!(f, "{v}"),
@@ -1038,6 +1115,7 @@ impl From<Value> for LuaValue {
     fn from(value: Value) -> Self {
         match value {
             Value::Boolean(v) => LuaValue::Boolean(v),
+            Value::Datetime(v) => LuaValue::Datetime(v),
             Value::Decimal(v) => LuaValue::Decimal(v),
             Value::Double(v) => LuaValue::Double(v.value),
             Value::Integer(v) => LuaValue::Integer(v),
@@ -1056,6 +1134,7 @@ impl From<LuaValue> for Value {
         match value {
             LuaValue::Unsigned(v) => Value::Unsigned(v),
             LuaValue::Integer(v) => Value::Integer(v),
+            LuaValue::Datetime(v) => Value::Datetime(v),
             LuaValue::Decimal(v) => Value::Decimal(v),
             LuaValue::Double(v) => {
                 if v.is_nan() {
@@ -1083,6 +1162,7 @@ impl From<Value> for String {
         match v {
             Value::Unsigned(v) => v.to_string(),
             Value::Integer(v) => v.to_string(),
+            Value::Datetime(v) => v.to_string(),
             Value::Decimal(v) => v.to_string(),
             Value::Double(v) => v.to_string(),
             Value::Boolean(v) => v.to_string(),
@@ -1101,6 +1181,7 @@ impl<L: tlua::AsLua> tlua::Push<L> for Value {
         match self {
             Value::Unsigned(v) => v.push_to_lua(lua),
             Value::Integer(v) => v.push_to_lua(lua),
+            Value::Datetime(v) => v.push_to_lua(lua),
             Value::Decimal(v) => v.push_to_lua(lua),
             Value::Double(v) => v.push_to_lua(lua),
             Value::Boolean(v) => v.push_to_lua(lua),
@@ -1122,6 +1203,7 @@ where
         match self {
             Value::Unsigned(v) => v.push_into_lua(lua),
             Value::Integer(v) => v.push_into_lua(lua),
+            Value::Datetime(v) => v.push_into_lua(lua),
             Value::Decimal(v) => v.push_into_lua(lua),
             Value::Double(v) => v.push_into_lua(lua),
             Value::Boolean(v) => v.push_into_lua(lua),