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),