diff --git a/doc/sql/query.ebnf b/doc/sql/query.ebnf index 6632d84c378d1b95b3f330e60e242fb8d3201091..30306b7959e5f3b78910e7a266a7fba8ad536517 100644 --- a/doc/sql/query.ebnf +++ b/doc/sql/query.ebnf @@ -70,6 +70,7 @@ type ::= 'ANY' | 'STRING' | 'TEXT' | 'UNSIGNED' + | 'UUID' | 'VARCHAR' ('(' length ')')? parameter ::= '$' unsigned | '?' delete ::= 'DELETE' 'FROM' table ('WHERE' expression)? diff --git a/sbroad-cartridge/test_app/test/data/config.yml b/sbroad-cartridge/test_app/test/data/config.yml index 7bfe4c14708cd088f816b692cccb9b5fadcb11fa..d6dbe23269b2bf7d6084d8829559a71604608864 100644 --- a/sbroad-cartridge/test_app/test/data/config.yml +++ b/sbroad-cartridge/test_app/test/data/config.yml @@ -1372,3 +1372,34 @@ schema: engine: memtx sharding_key: - id + uuid_t: + format: + - type: uuid + name: id + is_nullable: false + - type: string + name: name + is_nullable: false + - type: unsigned + name: bucket_id + is_nullable: false + temporary: false + indexes: + - unique: true + parts: + - path: id + is_nullable: false + type: uuid + 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: + - id diff --git a/sbroad-cartridge/test_app/test/integration/uuid_test.lua b/sbroad-cartridge/test_app/test/integration/uuid_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..2a64c8e8595445adfbda516f0395c0b1ff7748ee --- /dev/null +++ b/sbroad-cartridge/test_app/test/integration/uuid_test.lua @@ -0,0 +1,126 @@ +local t = require('luatest') +local g = t.group('integration_api.insert_uuid_sql') + +local helper = require('test.helper.cluster_no_replication') + +local UUID = require("uuid") +local uuid1 = UUID() +local uuid2 = UUID() +local uuid3 = "fb1649a4-d2db-4df4-a24d-2b4e81ee8a41" + +g.before_all( + function() + helper.start_test_cluster(helper.cluster_config) + + local api = helper.cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", + { + [[ + INSERT INTO "uuid_t"("id", "name") + VALUES (?, ?), (?, ?), (?, ?) + ]], + { uuid1, "test-1", uuid2, "test-2", uuid3, "test-3" } + } + ) + t.assert_equals(err, nil) + t.assert_equals(r, {row_count = 3}) + end +) + +g.after_all(function() + helper.stop_test_cluster() +end) + +g.test_uuid_sql_where_uuid1 = function () + local api = helper.cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", + { + [[SELECT "id", "name" FROM "uuid_t" WHERE "id" = ?]], + {uuid1} + } + ) + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "uuid"}, + {name = "name", type = "string"}, + }, + rows = { + { uuid1, 'test-1' } + }, + }) +end + +g.test_uuid_sql_where_cast_uuid_test1 = function () + local api = helper.cluster:server("api-1").net_box + + local r, err = api:call( + "sbroad.execute", + { + [[SELECT CAST("id" as Text) as "id", "name" FROM "uuid_t" WHERE "id" = ? ]], + { uuid1 } + } + ) + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "string"}, + {name = "name", type = "string"}, + }, + rows = { + { string.format('%s', uuid1), 'test-1' } + }, + }) +end + +g.test_uuid_sql_where_cast_uuid_test2 = function () + local api = helper.cluster:server("api-1").net_box + + local r, err = api:call("sbroad.execute", + { + [[SELECT * FROM "uuid_t" WHERE CAST("id" as Text) IN (?) ]], + { uuid3 } + } + ) + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "uuid"}, + {name = "name", type = "string"}, + }, + rows = { + { uuid3, 'test-3' } + }, + }) +end + +g.test_uuid_sql_where_cast_uuid_test3 = function () + local api = helper.cluster:server("api-1").net_box + + local r, err = api:call( + "sbroad.execute", + { + [[ + SELECT CAST("id" as Uuid) as "id", "name" + FROM ( + SELECT CAST("id" as Text) as "id", "name" + FROM "uuid_t" where "id" = ? + ) + ]], + { uuid2 } + } + ) + + t.assert_equals(err, nil) + t.assert_equals(r, { + metadata = { + {name = "id", type = "uuid"}, + {name = "name", type = "string"}, + }, + rows = { + { uuid2, 'test-2' } + }, + }) +end diff --git a/sbroad-core/src/cbo/selectivity.rs b/sbroad-core/src/cbo/selectivity.rs index 9672f8ce06408f85076037b0a39ac3ffdd263593..c4ca2e9e2a25f6221aaf350d9beef9503755ebdf 100644 --- a/sbroad-core/src/cbo/selectivity.rs +++ b/sbroad-core/src/cbo/selectivity.rs @@ -338,6 +338,9 @@ pub fn calculate_filter_selectivity( "Unable to calculate selectivity for array type column", )), )), + Type::Uuid => { + todo!("Don't know what to do here") + } } } diff --git a/sbroad-core/src/frontend/sql.rs b/sbroad-core/src/frontend/sql.rs index 689fb9a4df774fa1fe44b46728f909bb47c3b832..e96d8c1ccb9aaf04dc9a9d4ea1f5e96dc755375d 100644 --- a/sbroad-core/src/frontend/sql.rs +++ b/sbroad-core/src/frontend/sql.rs @@ -157,6 +157,7 @@ fn parse_proc_params( Rule::TypeNumber => RelationType::Number, Rule::TypeScalar => RelationType::Scalar, Rule::TypeString | Rule::TypeText | Rule::TypeVarchar => RelationType::String, + Rule::TypeUuid => RelationType::Uuid, Rule::TypeUnsigned => RelationType::Unsigned, _ => unreachable!("Unexpected node: {type_node:?}"), }; @@ -384,6 +385,9 @@ fn parse_create_table(ast: &AbstractSyntaxTree, node: &ParseNode) -> Result<Ddl, Rule::TypeUnsigned => { column_def.data_type = RelationType::Unsigned; } + Rule::TypeUuid => { + column_def.data_type = RelationType::Uuid; + } _ => { return Err(SbroadError::Invalid( Entity::Node, diff --git a/sbroad-core/src/frontend/sql/query.pest b/sbroad-core/src/frontend/sql/query.pest index ae0fd58c0e8e885e3352101379ef30b574e44968..c394039a908d8d7f8b6152cab1e19e25ceb0eef7 100644 --- a/sbroad-core/src/frontend/sql/query.pest +++ b/sbroad-core/src/frontend/sql/query.pest @@ -107,7 +107,7 @@ Query = { (SelectWithOptionalContinuation | Values | Insert | Update | Delete) ~ UnionAllContinuation = { ^"union" ~ ^"all" ~ Select } Select = { ^"select" ~ Projection ~ ^"from" ~ Scan ~ - Join? ~ WhereClause? ~ + Join? ~ WhereClause? ~ (^"group" ~ ^"by" ~ GroupBy)? ~ (^"having" ~ Having)? } @@ -203,7 +203,7 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } Cast = { ^"cast" ~ "(" ~ Expr ~ ^"as" ~ TypeCast ~ ")" } TypeCast = _{ TypeAny | ColumnDefType } ColumnDefType = { TypeBool | TypeDecimal | TypeDouble | TypeInt | TypeNumber - | TypeScalar | TypeString | TypeText | TypeUnsigned | TypeVarchar } + | TypeScalar | TypeString | TypeText | TypeUnsigned | TypeVarchar | TypeUuid } TypeAny = { ^"any" } TypeBool = { (^"boolean" | ^"bool") } TypeDecimal = { ^"decimal" } @@ -213,6 +213,7 @@ Expr = { ExprAtomValue ~ (ExprInfixOp ~ ExprAtomValue)* } TypeScalar = { ^"scalar" } TypeString = { ^"string" } TypeText = { ^"text" } + TypeUuid = { ^"uuid" } TypeUnsigned = { ^"unsigned" } TypeVarchar = { ^"varchar" ~ "(" ~ Unsigned ~ ")" } UnaryOperator = _{ Exists } diff --git a/sbroad-core/src/ir/expression/cast.rs b/sbroad-core/src/ir/expression/cast.rs index dc1aa03e6778b75e37ffcc22625f560d7702a060..8e4269c35871306b24e95f38169a0bbeb4e7b02a 100644 --- a/sbroad-core/src/ir/expression/cast.rs +++ b/sbroad-core/src/ir/expression/cast.rs @@ -19,6 +19,7 @@ pub enum Type { Scalar, String, Text, + Uuid, Unsigned, Varchar(usize), } @@ -40,6 +41,7 @@ impl TryFrom<&Rule> for Type { Rule::TypeScalar => Ok(Type::Scalar), Rule::TypeString => Ok(Type::String), Rule::TypeText => Ok(Type::Text), + Rule::TypeUuid => Ok(Type::Uuid), Rule::TypeUnsigned => Ok(Type::Unsigned), _ => Err(SbroadError::Unsupported( Entity::Type, @@ -64,6 +66,7 @@ impl TryFrom<&RelationType> for Type { RelationType::Unsigned => Ok(Type::Unsigned), RelationType::Map => Ok(Type::Map), RelationType::Any => Ok(Type::Any), + RelationType::Uuid => Ok(Type::Uuid), RelationType::Array => Err(SbroadError::Unsupported( Entity::Type, Some("array int the cast operation".to_string()), @@ -85,6 +88,7 @@ impl From<&Type> for String { Type::Scalar => "scalar".to_string(), Type::String => "string".to_string(), Type::Text => "text".to_string(), + Type::Uuid => "uuid".to_string(), Type::Unsigned => "unsigned".to_string(), Type::Varchar(length) => format!("varchar({length})"), } @@ -108,6 +112,7 @@ impl Type { Type::Double => RelationType::Double, Type::Integer => RelationType::Integer, Type::Number => RelationType::Number, + Type::Uuid => RelationType::Uuid, Type::String | Type::Text | Type::Varchar(_) => RelationType::String, Type::Unsigned => RelationType::Unsigned, } diff --git a/sbroad-core/src/ir/relation.rs b/sbroad-core/src/ir/relation.rs index 49eff9ec79df6bb4d37cf32bdfcc4e5d01ec88d3..9ae2e0836cc0931a04709e0b137c67e25923e3da 100644 --- a/sbroad-core/src/ir/relation.rs +++ b/sbroad-core/src/ir/relation.rs @@ -42,6 +42,7 @@ pub enum Type { Scalar, String, Number, + Uuid, Unsigned, } @@ -56,6 +57,7 @@ impl fmt::Display for Type { Type::Scalar => write!(f, "scalar"), Type::String => write!(f, "string"), Type::Number => write!(f, "number"), + Type::Uuid => write!(f, "uuid"), Type::Unsigned => write!(f, "unsigned"), Type::Any => write!(f, "any"), Type::Map => write!(f, "map"), @@ -72,6 +74,7 @@ impl From<&Type> for FieldType { Type::Integer => FieldType::Integer, Type::Number => FieldType::Number, Type::Scalar => FieldType::Scalar, + Type::Uuid => FieldType::Uuid, Type::String => FieldType::String, Type::Unsigned => FieldType::Unsigned, Type::Array => FieldType::Array, @@ -91,6 +94,7 @@ impl From<&Type> for SpaceFieldType { Type::Number => SpaceFieldType::Number, Type::Scalar => SpaceFieldType::Scalar, Type::String => SpaceFieldType::String, + Type::Uuid => SpaceFieldType::Uuid, Type::Unsigned => SpaceFieldType::Unsigned, Type::Array => SpaceFieldType::Array, Type::Any => SpaceFieldType::Any, @@ -142,6 +146,7 @@ impl Type { "number" => Ok(Type::Number), "scalar" => Ok(Type::Scalar), "string" | "text" => Ok(Type::String), + "uuid" => Ok(Type::Uuid), "unsigned" => Ok(Type::Unsigned), "array" => Ok(Type::Array), "any" => Ok(Type::Any), @@ -158,7 +163,7 @@ impl Type { pub fn new_from_possibly_incorrect(s: &str) -> Result<Self, SbroadError> { match s.to_string().to_lowercase().as_str() { "boolean" | "decimal" | "double" | "integer" | "number" | "numeric" | "scalar" - | "string" | "text" | "unsigned" => Ok(Type::Scalar), + | "string" | "uuid" | "text" | "unsigned" => Ok(Type::Scalar), "array" => Ok(Type::Array), "map" => Ok(Type::Map), "any" => Ok(Type::Any), @@ -182,6 +187,7 @@ impl Type { | Type::Number | Type::Scalar | Type::String + | Type::Uuid | Type::Unsigned ) } @@ -198,7 +204,7 @@ impl Type { Type::Double | Type::Integer | Type::Unsigned | Type::Decimal | Type::Number, ) | (Type::Scalar, Type::Scalar) - | (Type::String, Type::String) + | (Type::String | Type::Uuid, Type::String | Type::Uuid) ) } } @@ -248,6 +254,7 @@ impl From<Column> for Field { Type::Number => Field::number(column.name), Type::Scalar => Field::scalar(column.name), Type::String => Field::string(column.name), + Type::Uuid => Field::uuid(column.name), Type::Unsigned => Field::unsigned(column.name), Type::Array => Field::array(column.name), Type::Any => Field::any(column.name), @@ -286,6 +293,7 @@ impl SerSerialize for Column { Type::Number => map.serialize_entry("type", "number")?, Type::Scalar => map.serialize_entry("type", "scalar")?, Type::String => map.serialize_entry("type", "string")?, + Type::Uuid => map.serialize_entry("type", "uuid")?, Type::Unsigned => map.serialize_entry("type", "unsigned")?, Type::Array => map.serialize_entry("type", "array")?, Type::Any => map.serialize_entry("type", "any")?, @@ -349,6 +357,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")), } } diff --git a/sbroad-core/src/ir/transformation/equality_propagation/tests.rs b/sbroad-core/src/ir/transformation/equality_propagation/tests.rs index 8581dce8281711d1d4556f8e36d89970cd8683a6..f1cf25a1678935bdccdefee136bef8fc4f41b85a 100644 --- a/sbroad-core/src/ir/transformation/equality_propagation/tests.rs +++ b/sbroad-core/src/ir/transformation/equality_propagation/tests.rs @@ -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"."b") and ("t"."b") = ("t"."a")"#, - r#"and ("t"."a") = ("t"."d")"#, + r#"and ("t"."c") = ("t"."d") and ("t"."d") = ("t"."b")"#, + r#"and ("t"."b") = ("t"."a")"#, ), vec![ Value::from(1_u64), diff --git a/sbroad-core/src/ir/value.rs b/sbroad-core/src/ir/value.rs index 1151ff201dc4d608a66074324ed145ea856cf3b9..bd7addf4b163cc3e6fca004989bdec8873d34abc 100644 --- a/sbroad-core/src/ir/value.rs +++ b/sbroad-core/src/ir/value.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; use tarantool::decimal::Decimal; use tarantool::tlua::{self, LuaRead}; use tarantool::tuple::{FieldType, KeyDefPart}; +use tarantool::uuid::Uuid; use crate::error; use crate::errors::{Action, Entity, SbroadError}; @@ -130,6 +131,8 @@ pub enum Value { Unsigned(u64), /// Tuple type Tuple(Tuple), + /// Uuid type + Uuid(Uuid), } /// Custom Ordering using Trivalent instead of simple Equal. @@ -185,6 +188,7 @@ impl fmt::Display for Value { Value::Decimal(v) => fmt::Display::fmt(v, f), Value::String(v) => write!(f, "'{v}'"), Value::Tuple(v) => write!(f, "{v}"), + Value::Uuid(v) => fmt::Display::fmt(v, f), } } } @@ -275,6 +279,12 @@ impl From<Trivalent> for Value { } } +impl From<Uuid> for Value { + fn from(v: Uuid) -> Self { + Value::Uuid(v) + } +} + /// Helper function to extract inner numerical value from `value` and cast it to `Decimal`. /// /// # Errors @@ -439,11 +449,14 @@ impl Value { | Value::Decimal(_) | Value::Double(_) | Value::String(_) + | Value::Uuid(_) | Value::Tuple(_) => Trivalent::False, }, Value::Null => Trivalent::Unknown, Value::Integer(s) => match other { - Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => Trivalent::False, + Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => { + Trivalent::False + } Value::Null => Trivalent::Unknown, Value::Integer(o) => (s == o).into(), Value::Decimal(o) => (&Decimal::from(*s) == o).into(), @@ -454,7 +467,9 @@ impl Value { Value::Unsigned(o) => (&Decimal::from(*s) == o).into(), }, Value::Double(s) => match other { - Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => Trivalent::False, + Value::Boolean(_) | Value::String(_) | Value::Tuple(_) | Value::Uuid(_) => { + 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. @@ -466,7 +481,9 @@ impl Value { } }, Value::Decimal(s) => match other { - Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => Trivalent::False, + Value::Boolean(_) | Value::String(_) | Value::Tuple(_) | Value::Uuid(_) => { + Trivalent::False + } Value::Null => Trivalent::Unknown, Value::Integer(o) => (s == &Decimal::from(*o)).into(), Value::Decimal(o) => (s == o).into(), @@ -475,7 +492,9 @@ impl Value { Value::Unsigned(o) => (s == &Decimal::from(*o)).into(), }, Value::Unsigned(s) => match other { - Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => Trivalent::False, + Value::Boolean(_) | Value::String(_) | Value::Uuid(_) | Value::Tuple(_) => { + Trivalent::False + } Value::Null => Trivalent::Unknown, Value::Integer(o) => (Decimal::from(*s) == *o).into(), Value::Decimal(o) => (&Decimal::from(*s) == o).into(), @@ -491,6 +510,7 @@ impl Value { | Value::Decimal(_) | Value::Double(_) | Value::Unsigned(_) + | Value::Uuid(_) | Value::Tuple(_) => Trivalent::False, Value::Null => Trivalent::Unknown, Value::String(o) => s.eq(o).into(), @@ -502,8 +522,20 @@ impl Value { | Value::Double(_) | Value::Unsigned(_) | Value::String(_) + | Value::Uuid(_) + | Value::Tuple(_) => Trivalent::False, + Value::Null => Trivalent::Unknown, + }, + Value::Uuid(s) => match other { + Value::Boolean(_) + | Value::Integer(_) + | Value::Decimal(_) + | Value::Double(_) + | Value::String(_) + | Value::Unsigned(_) | Value::Tuple(_) => Trivalent::False, Value::Null => Trivalent::Unknown, + Value::Uuid(o) => s.eq(o).into(), }, } } @@ -518,6 +550,7 @@ impl Value { Value::Unsigned(_) => FieldType::Unsigned, Value::String(_) => FieldType::String, Value::Tuple(_) => FieldType::Array, + Value::Uuid(_) => FieldType::Uuid, Value::Null => FieldType::Any, }; KeyDefPart { @@ -529,6 +562,149 @@ impl Value { } } + /// Compares two values. + /// The result uses four-valued logic (standard `Ordering` variants and + /// `Unknown` in case `Null` was met). + /// + /// Returns `None` in case of + /// * String casting Error or types mismatch. + /// * Float `NaN` comparison occurred. + #[must_use] + #[allow(clippy::too_many_lines)] + pub fn partial_cmp(&self, other: &Value) -> Option<TrivalentOrdering> { + match self { + Value::Boolean(s) => match other { + Value::Boolean(o) => TrivalentOrdering::from(s.cmp(o)).into(), + Value::Null => TrivalentOrdering::Unknown.into(), + Value::Unsigned(_) + | Value::Integer(_) + | Value::Decimal(_) + | Value::Double(_) + | Value::String(_) + | Value::Uuid(_) + | Value::Tuple(_) => None, + }, + Value::Null => TrivalentOrdering::Unknown.into(), + Value::Integer(s) => match other { + Value::Boolean(_) | 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(), + // If double can't be converted to decimal without error then it is not equal to integer. + Value::Double(o) => { + let self_converted = Decimal::from_str(&format!("{s}")); + let other_converted = Decimal::from_str(&format!("{o}")); + match (self_converted, other_converted) { + (Ok(d1), Ok(d2)) => TrivalentOrdering::from(d1.cmp(&d2)).into(), + _ => None, + } + } + Value::Unsigned(o) => { + TrivalentOrdering::from(Decimal::from(*s).cmp(&Decimal::from(*o))).into() + } + }, + Value::Double(s) => match other { + Value::Boolean(_) | 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)) { + TrivalentOrdering::from(ord).into() + } else { + None + } + } + // If double can't be converted to decimal without error then it is not equal to decimal. + Value::Decimal(o) => { + if let Ok(d) = Decimal::from_str(&format!("{s}")) { + TrivalentOrdering::from(d.cmp(o)).into() + } else { + None + } + } + Value::Double(o) => { + if let Some(ord) = s.partial_cmp(o) { + TrivalentOrdering::from(ord).into() + } else { + None + } + } + // If double can't be converted to decimal without error then it is not equal to unsigned. + Value::Unsigned(o) => { + if let Ok(d) = Decimal::from_str(&format!("{s}")) { + TrivalentOrdering::from(d.cmp(&Decimal::from(*o))).into() + } else { + None + } + } + }, + Value::Decimal(s) => match other { + Value::Boolean(_) | 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(), + // If double can't be converted to decimal without error then it is not equal to decimal. + Value::Double(o) => { + if let Ok(d) = Decimal::from_str(&format!("{o}")) { + TrivalentOrdering::from(s.cmp(&d)).into() + } else { + None + } + } + 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::Null => TrivalentOrdering::Unknown.into(), + Value::Integer(o) => { + TrivalentOrdering::from(Decimal::from(*s).cmp(&Decimal::from(*o))).into() + } + Value::Decimal(o) => TrivalentOrdering::from(Decimal::from(*s).cmp(o)).into(), + // If double can't be converted to decimal without error then it is not equal to unsigned. + Value::Double(o) => { + if let Ok(d) = Decimal::from_str(&format!("{o}")) { + TrivalentOrdering::from(Decimal::from(*s).cmp(&d)).into() + } else { + None + } + } + Value::Unsigned(o) => TrivalentOrdering::from(s.cmp(o)).into(), + }, + Value::String(s) => match other { + Value::Boolean(_) + | Value::Integer(_) + | Value::Decimal(_) + | Value::Double(_) + | Value::Unsigned(_) + | Value::Uuid(_) + | Value::Tuple(_) => None, + Value::Null => TrivalentOrdering::Unknown.into(), + Value::String(o) => TrivalentOrdering::from(s.cmp(o)).into(), + }, + Value::Uuid(u) => match other { + Value::Boolean(_) + | Value::Integer(_) + | Value::Decimal(_) + | Value::Double(_) + | Value::Unsigned(_) + | Value::String(_) + | Value::Tuple(_) => None, + Value::Null => TrivalentOrdering::Unknown.into(), + Value::Uuid(o) => TrivalentOrdering::from(u.cmp(o)).into(), + }, + Value::Tuple(_) => match other { + Value::Boolean(_) + | Value::Integer(_) + | Value::Decimal(_) + | Value::Double(_) + | Value::Unsigned(_) + | Value::String(_) + | Value::Uuid(_) + | Value::Tuple(_) => None, + Value::Null => TrivalentOrdering::Unknown.into(), + }, + } + } + /// Cast a value to a different type and wrap into encoded value. /// If the target type is the same as the current type, the value /// is returned by reference. Otherwise, the value is cloned. @@ -647,6 +823,23 @@ impl Value { format!("{self:?} into string"), )), }, + Type::Uuid => match self { + Value::Uuid(_) => Ok(self.into()), + Value::String(v) => Ok(Value::Uuid(Uuid::parse_str(v).map_err(|e| { + SbroadError::FailedTo( + Action::Serialize, + Some(Entity::Value), + format!("uuid {v} into string: {e}"), + ) + })?) + .into()), + Value::Null => Ok(Value::Null.into()), + _ => Err(SbroadError::FailedTo( + Action::Serialize, + Some(Entity::Value), + format!("{self:?} into uuid"), + )), + }, Type::Number => match self { Value::Integer(_) | Value::Decimal(_) | Value::Double(_) | Value::Unsigned(_) => { Ok(self.into()) @@ -708,6 +901,7 @@ impl Value { Value::Boolean(_) => Type::Boolean, Value::String(_) => Type::String, Value::Tuple(_) => Type::Array, + Value::Uuid(_) => Type::Uuid, Value::Null => Type::Scalar, } } @@ -728,6 +922,7 @@ impl ToHashString for Value { Value::Boolean(v) => v.to_string(), Value::String(v) => v.to_string(), Value::Tuple(v) => v.to_string(), + Value::Uuid(v) => v.to_string(), Value::Null => "NULL".to_string(), } } @@ -777,6 +972,7 @@ pub enum MsgPackValue<'v> { Unsigned(&'v u64), String(&'v String), Tuple(&'v Tuple), + Uuid(&'v Uuid), Null(()), } @@ -790,6 +986,7 @@ impl<'v> From<&'v Value> for MsgPackValue<'v> { Value::Null => MsgPackValue::Null(()), Value::String(v) => MsgPackValue::String(v), Value::Tuple(v) => MsgPackValue::Tuple(v), + Value::Uuid(v) => MsgPackValue::Uuid(v), Value::Unsigned(v) => MsgPackValue::Unsigned(v), } } @@ -805,6 +1002,7 @@ pub enum LuaValue { Integer(i64), Unsigned(u64), String(String), + Uuid(Uuid), Tuple(Tuple), Null(()), } @@ -819,6 +1017,7 @@ impl fmt::Display for LuaValue { LuaValue::Unsigned(v) => write!(f, "{v}"), LuaValue::String(v) => write!(f, "'{v}'"), LuaValue::Tuple(v) => write!(f, "{v}"), + LuaValue::Uuid(v) => write!(f, "{v}"), LuaValue::Null(()) => write!(f, "NULL"), } } @@ -834,6 +1033,7 @@ impl From<Value> for LuaValue { Value::Null => LuaValue::Null(()), Value::String(v) => LuaValue::String(v), Value::Tuple(v) => LuaValue::Tuple(v), + Value::Uuid(v) => LuaValue::Uuid(v), Value::Unsigned(v) => LuaValue::Unsigned(v), } } @@ -861,6 +1061,7 @@ impl From<LuaValue> for Value { LuaValue::Boolean(v) => Value::Boolean(v), LuaValue::String(v) => Value::String(v), LuaValue::Tuple(v) => Value::Tuple(v), + LuaValue::Uuid(v) => Value::Uuid(v), LuaValue::Null(()) => Value::Null, } } @@ -876,6 +1077,7 @@ impl From<Value> for String { Value::Boolean(v) => v.to_string(), Value::String(v) => v, Value::Tuple(v) => v.to_string(), + Value::Uuid(v) => v.to_string(), Value::Null => "NULL".to_string(), } } @@ -893,6 +1095,7 @@ impl<L: tlua::AsLua> tlua::Push<L> for Value { Value::Boolean(v) => v.push_to_lua(lua), Value::String(v) => v.push_to_lua(lua), Value::Tuple(v) => v.push_to_lua(lua), + Value::Uuid(v) => v.push_to_lua(lua), Value::Null => tlua::Null.push_to_lua(lua), } } @@ -913,6 +1116,7 @@ where Value::Boolean(v) => v.push_into_lua(lua), Value::String(v) => v.push_into_lua(lua), Value::Tuple(v) => v.push_into_lua(lua), + Value::Uuid(v) => v.push_into_lua(lua), Value::Null => tlua::Null.push_into_lua(lua), } } @@ -970,6 +1174,10 @@ where Ok(v) => return Ok(Self::Tuple(v)), Err((lua, _)) => lua, }; + let lua = match tlua::LuaRead::lua_read_at_position(lua, index) { + Ok(v) => return Ok(Self::Uuid(v)), + Err((lua, _)) => lua, + }; let Err((lua, _)) = tlua::Null::lua_read_at_position(lua, index) else { return Ok(Self::Null); }; diff --git a/sbroad-core/src/ir/value/tests.rs b/sbroad-core/src/ir/value/tests.rs index bda293b4aec4c3941a15b289def1714d35758fe7..20286e472aff357f64fcd194240c91f4b429f2d4 100644 --- a/sbroad-core/src/ir/value/tests.rs +++ b/sbroad-core/src/ir/value/tests.rs @@ -9,6 +9,49 @@ fn boolean() { assert_ne!(Value::from(true), Value::from(false)); } +#[test] +fn uuid() { + let uid = uuid::Uuid::new_v4(); + let t_uid_1 = Uuid::parse_str(&uid.to_string()).unwrap(); + let t_uid_2 = Uuid::parse_str(&uuid::Uuid::new_v4().to_string()).unwrap(); + let v_uid = Value::Uuid(t_uid_1); + + assert_eq!(Value::from(t_uid_1), v_uid); + assert_eq!(format!("{}", v_uid), uid.to_string()); + assert_eq!(v_uid.get_type(), Type::Uuid); + assert_eq!(v_uid.eq(&Value::Uuid(t_uid_1)), Trivalent::True); + assert_eq!(v_uid.eq(&Value::Uuid(t_uid_2)), Trivalent::False); + assert_eq!( + v_uid.eq(&Value::String(t_uid_1.to_string())), + Trivalent::False + ); + assert_eq!( + v_uid.partial_cmp(&Value::Uuid(t_uid_1)), + Some(TrivalentOrdering::Equal) + ); + assert_ne!( + v_uid.partial_cmp(&Value::Uuid(t_uid_2)), + Some(TrivalentOrdering::Equal) + ); + assert_eq!( + Value::String(uid.to_string()).cast(&Type::Uuid).is_ok(), + true + ); + assert_eq!(v_uid.partial_cmp(&Value::String(t_uid_2.to_string())), None); +} + +fn uuid_negative() { + assert_eq!( + Value::String("hello".to_string()) + .cast(&Type::Uuid) + .unwrap_err(), + SbroadError::FailedTo( + Action::Serialize, + Some(Entity::Value), + "uuid hello into string: invalid length: expected one of [36, 32], found 5".to_string() + ) + ); +} #[test] fn decimal() { assert_eq!(