diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d346551f..a066f35f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -23,16 +23,16 @@ - + - - - - - - - - + + + + + + + + - + + + + + + + + + @@ -138,7 +176,7 @@ @@ -198,6 +243,7 @@ - \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9571ebe0..a29996d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ nalgebra = "0.31.1" approx = "0.5.1" unicode-normalization = "0.1.21" thiserror = "1.0.34" +uuid = { version = "1.1.2", features = ["v1", "v4", "serde"] } cozorocks = { path = "cozorocks" } #[target.'cfg(not(target_env = "msvc"))'.dependencies] diff --git a/docs/source/datatypes.rst b/docs/source/datatypes.rst index 416054ea..a50a4ada 100644 --- a/docs/source/datatypes.rst +++ b/docs/source/datatypes.rst @@ -13,6 +13,7 @@ A runtime value in Cozo can be of the following *value-types*: * ``Number`` * ``String`` * ``Bytes`` +* ``Uuid`` * ``List`` ``Number`` can be ``Float`` (double precision) or ``Int`` (signed, 64 bits). Cozo will auto-promote ``Int`` to ``Float`` when necessary. @@ -28,6 +29,7 @@ Within each type values are *compared* according to logic custom to each type: * Lists are ordered lexicographically by their elements; * Bytes are compared lexicographically; * Strings are ordered lexicographically by their UTF-8 byte representations. +* If two UUIDs are both of version one, they are compared based on their timestamp. Otherwise, or when the timestamp comparison results in a tie, they are compared by their bytes. .. WARNING:: diff --git a/docs/source/functions.rst b/docs/source/functions.rst index 0b2382e7..1fa45f26 100644 --- a/docs/source/functions.rst +++ b/docs/source/functions.rst @@ -465,6 +465,13 @@ Type checking and conversions * ``PI`` is converted to pi (3.14159...); * ``E`` is converted to the base of natural logarithms, or Euler's constant (2.71828...). +.. function:: to_uuid(x) + + Tries to convert ``x`` to a UUID. The input must either be a hyphenated UUID string representation or already a UUID for it to succeed. + +.. function:: uuid_timestamp(x) + + Extracts the timestamp from a UUID version 1, as nanoseconds since the UNIX epoch divided by 100. If the UUID is not of version 1, ``null`` is returned. If ``x`` is not a UUID, an error is raised. .. function:: is_null(x) @@ -506,6 +513,10 @@ Type checking and conversions Checks for strings. +.. function:: is_uuid(x) + + Checks for UUIDs. + ----------------- Random functions ----------------- @@ -529,6 +540,13 @@ Random functions Randomly chooses an element from ``list`` and returns it. If the list is empty, it returns ``null``. +.. function:: rand_uuid_v1() + + Generate a random UUID, version 1 (random bits plus timestamp). + +.. function:: rand_uuid_v4() + + Generate a random UUID, version 4 (completely random bits). ------------------ Regex functions diff --git a/src/data/expr.rs b/src/data/expr.rs index e1717825..9bacbdd2 100644 --- a/src/data/expr.rs +++ b/src/data/expr.rs @@ -544,6 +544,7 @@ pub(crate) fn get_op(name: &str) -> Option<&'static Op> { "is_finite" => &OP_IS_FINITE, "is_infinite" => &OP_IS_INFINITE, "is_nan" => &OP_IS_NAN, + "is_uuid" => &OP_IS_UUID, "length" => &OP_LENGTH, "sorted" => &OP_SORTED, "reverse" => &OP_REVERSE, @@ -581,6 +582,10 @@ pub(crate) fn get_op(name: &str) -> Option<&'static Op> { "union" => &OP_UNION, "intersection" => &OP_INTERSECTION, "difference" => &OP_DIFFERENCE, + "to_uuid" => &OP_TO_UUID, + "rand_uuid_v1" => &OP_RAND_UUID_V1, + "rand_uuid_v4" => &OP_RAND_UUID_V4, + "uuid_timestamp" => &OP_UUID_TIMESTAMP, _ => return None, }) } diff --git a/src/data/functions.rs b/src/data/functions.rs index af995120..7ad12c1a 100644 --- a/src/data/functions.rs +++ b/src/data/functions.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use std::ops::{Div, Rem}; use std::str::FromStr; +use std::time::{SystemTime, UNIX_EPOCH}; use itertools::Itertools; use miette::{bail, ensure, miette, Result}; @@ -8,10 +9,11 @@ use num_traits::FloatConst; use rand::prelude::*; use smartstring::SmartString; use unicode_normalization::UnicodeNormalization; +use uuid::v1::Timestamp; use crate::data::expr::Op; use crate::data::json::JsonValue; -use crate::data::value::{same_value_type, DataValue, Num, RegexWrapper}; +use crate::data::value::{DataValue, Num, RegexWrapper, same_value_type, UuidWrapper}; macro_rules! define_op { ($name:ident, $min_arity:expr, $vararg:expr) => { @@ -38,6 +40,11 @@ pub(crate) fn op_eq(args: &[DataValue]) -> Result { })) } +define_op!(OP_IS_UUID, 1, false); +pub(crate) fn op_is_uuid(args: &[DataValue]) -> Result { + Ok(DataValue::Bool(matches!(args[0], DataValue::Uuid(_)))) +} + define_op!(OP_IS_IN, 2, false); pub(crate) fn op_is_in(args: &[DataValue]) -> Result { let left = &args[0]; @@ -983,9 +990,9 @@ pub(crate) fn op_haversine(args: &[DataValue]) -> Result { let lon2 = args[3].get_float().ok_or_else(miette)?; let ret = 2. * f64::asin(f64::sqrt( - f64::sin((lat1 - lat2) / 2.).powi(2) - + f64::cos(lat1) * f64::cos(lat2) * f64::sin((lon1 - lon2) / 2.).powi(2), - )); + f64::sin((lat1 - lat2) / 2.).powi(2) + + f64::cos(lat1) * f64::cos(lat2) * f64::sin((lon1 - lon2) / 2.).powi(2), + )); Ok(DataValue::from(ret)) } @@ -998,9 +1005,9 @@ pub(crate) fn op_haversine_deg_input(args: &[DataValue]) -> Result { let lon2 = args[3].get_float().ok_or_else(miette)? * f64::PI() / 180.; let ret = 2. * f64::asin(f64::sqrt( - f64::sin((lat1 - lat2) / 2.).powi(2) - + f64::cos(lat1) * f64::cos(lat2) * f64::sin((lon1 - lon2) / 2.).powi(2), - )); + f64::sin((lat1 - lat2) / 2.).powi(2) + + f64::cos(lat1) * f64::cos(lat2) * f64::sin((lon1 - lon2) / 2.).powi(2), + )); Ok(DataValue::from(ret)) } @@ -1365,3 +1372,47 @@ pub(crate) fn op_intersection(args: &[DataValue]) -> Result { } Ok(DataValue::List(start.into_iter().collect())) } + +define_op!(OP_TO_UUID, 1, false); +pub(crate) fn op_to_uuid(args: &[DataValue]) -> Result { + match &args[0] { + d @ DataValue::Uuid(_u) => Ok(d.clone()), + DataValue::Str(s) => { + let id = uuid::Uuid::try_parse(s).map_err(|_| miette!("invalid UUID"))?; + Ok(DataValue::uuid(id)) + } + _ => bail!("'to_uuid' requires a string") + } +} + +define_op!(OP_RAND_UUID_V1, 0, false); +pub(crate) fn op_rand_uuid_v1(_args: &[DataValue]) -> Result { + let mut rng = rand::thread_rng(); + let uuid_ctx = uuid::v1::Context::new(rng.gen()); + let now = SystemTime::now(); + let since_epoch = now.duration_since(UNIX_EPOCH).unwrap(); + let ts = Timestamp::from_unix(uuid_ctx, since_epoch.as_secs(), since_epoch.subsec_nanos()); + let mut rand_vals = [0u8; 6]; + rng.fill(&mut rand_vals); + let id = uuid::Uuid::new_v1(ts, &rand_vals); + Ok(DataValue::uuid(id)) +} + +define_op!(OP_RAND_UUID_V4, 0, false); +pub(crate) fn op_rand_uuid_v4(_args: &[DataValue]) -> Result { + let id = uuid::Uuid::new_v4(); + Ok(DataValue::uuid(id)) +} + +define_op!(OP_UUID_TIMESTAMP, 1, false); +pub(crate) fn op_uuid_timestamp(args: &[DataValue]) -> Result { + Ok(match &args[0] { + DataValue::Uuid(UuidWrapper(id)) => { + match id.get_timestamp() { + None => DataValue::Null, + Some(t) => (t.to_unix().0 as i64).into() + } + } + _ => bail!("not an UUID") + }) +} \ No newline at end of file diff --git a/src/data/json.rs b/src/data/json.rs index 3b0255e4..b2fef4c8 100644 --- a/src/data/json.rs +++ b/src/data/json.rs @@ -88,9 +88,10 @@ impl From for JsonValue { } DataValue::Regex(r) => { json!(r.0.as_str()) - } // DataValue::Map(m) => { - // JsonValue::Array(m.into_iter().map(|(k, v)| json!([k, v])).collect()) - // } + } + DataValue::Uuid(u) => { + json!(u.0) + } } } } @@ -98,8 +99,8 @@ impl From for JsonValue { #[cfg(test)] mod tests { use serde_json::json; - use crate::data::json::JsonValue; + use crate::data::json::JsonValue; use crate::data::value::DataValue; #[test] diff --git a/src/data/tests/functions.rs b/src/data/tests/functions.rs index cd33ab71..ca3dbfa1 100644 --- a/src/data/tests/functions.rs +++ b/src/data/tests/functions.rs @@ -1,6 +1,7 @@ use approx::AbsDiffEq; use num_traits::FloatConst; use regex::Regex; +use smartstring::SmartString; use crate::data::functions::*; use crate::data::value::{DataValue, RegexWrapper}; @@ -116,7 +117,7 @@ fn test_is_in() { DataValue::from(1), DataValue::List(vec![DataValue::from(1), DataValue::from(2)]) ]) - .unwrap(), + .unwrap(), DataValue::Bool(true) ); assert_eq!( @@ -124,7 +125,7 @@ fn test_is_in() { DataValue::from(3), DataValue::List(vec![DataValue::from(1), DataValue::from(2)]) ]) - .unwrap(), + .unwrap(), DataValue::Bool(false) ); assert_eq!( @@ -240,7 +241,7 @@ fn test_comparators() { #[test] fn test_max_min() { - assert_eq!(op_max(&[DataValue::from(1),]).unwrap(), DataValue::from(1)); + assert_eq!(op_max(&[DataValue::from(1), ]).unwrap(), DataValue::from(1)); assert_eq!( op_max(&[ DataValue::from(1), @@ -248,7 +249,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(4) ); assert_eq!( @@ -258,7 +259,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(4) ); assert_eq!( @@ -268,12 +269,12 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4.0) ]) - .unwrap(), + .unwrap(), DataValue::from(4.0) ); assert!(op_max(&[DataValue::Bool(true)]).is_err()); - assert_eq!(op_min(&[DataValue::from(1),]).unwrap(), DataValue::from(1)); + assert_eq!(op_min(&[DataValue::from(1), ]).unwrap(), DataValue::from(1)); assert_eq!( op_min(&[ DataValue::from(1), @@ -281,7 +282,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(1) ); assert_eq!( @@ -291,7 +292,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(1.0) ); assert_eq!( @@ -301,7 +302,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4.0) ]) - .unwrap(), + .unwrap(), DataValue::from(1) ); assert!(op_max(&[DataValue::Bool(true)]).is_err()); @@ -555,7 +556,7 @@ fn test_bits() { DataValue::Bytes([0b111000].into()), DataValue::Bytes([0b010101].into()) ]) - .unwrap(), + .unwrap(), DataValue::Bytes([0b010000].into()) ); assert_eq!( @@ -563,7 +564,7 @@ fn test_bits() { DataValue::Bytes([0b111000].into()), DataValue::Bytes([0b010101].into()) ]) - .unwrap(), + .unwrap(), DataValue::Bytes([0b111101].into()) ); assert_eq!( @@ -575,7 +576,7 @@ fn test_bits() { DataValue::Bytes([0b111000].into()), DataValue::Bytes([0b010101].into()) ]) - .unwrap(), + .unwrap(), DataValue::Bytes([0b101101].into()) ); } @@ -613,11 +614,11 @@ fn test_concat() { DataValue::List(vec![DataValue::Bool(true), DataValue::Bool(false)]), DataValue::List(vec![DataValue::Bool(true)]) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Bool(true), DataValue::Bool(false), - DataValue::Bool(true) + DataValue::Bool(true), ]) ); } @@ -629,7 +630,7 @@ fn test_str_includes() { DataValue::Str("abcdef".into()), DataValue::Str("bcd".into()) ]) - .unwrap(), + .unwrap(), DataValue::Bool(true) ); assert_eq!( @@ -673,7 +674,7 @@ fn test_starts_ends_with() { DataValue::Str("abcdef".into()), DataValue::Str("abc".into()) ]) - .unwrap(), + .unwrap(), DataValue::Bool(true) ); assert_eq!( @@ -685,7 +686,7 @@ fn test_starts_ends_with() { DataValue::Str("abcdef".into()), DataValue::Str("def".into()) ]) - .unwrap(), + .unwrap(), DataValue::Bool(true) ); assert_eq!( @@ -701,7 +702,7 @@ fn test_regex() { DataValue::Str("abcdef".into()), DataValue::Regex(RegexWrapper(Regex::new("c.e").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Bool(true) ); @@ -710,7 +711,7 @@ fn test_regex() { DataValue::Str("abcdef".into()), DataValue::Regex(RegexWrapper(Regex::new("c.ef$").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Bool(true) ); @@ -719,7 +720,7 @@ fn test_regex() { DataValue::Str("abcdef".into()), DataValue::Regex(RegexWrapper(Regex::new("c.e$").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Bool(false) ); @@ -729,7 +730,7 @@ fn test_regex() { DataValue::Regex(RegexWrapper(Regex::new("[be]").unwrap())), DataValue::Str("x".into()) ]) - .unwrap(), + .unwrap(), DataValue::Str("axcdef".into()) ); @@ -739,7 +740,7 @@ fn test_regex() { DataValue::Regex(RegexWrapper(Regex::new("[be]").unwrap())), DataValue::Str("x".into()) ]) - .unwrap(), + .unwrap(), DataValue::Str("axcdxf".into()) ); assert_eq!( @@ -747,7 +748,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("[xayef]|(GH)").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Str("a".into()), DataValue::Str("e".into()), @@ -760,7 +761,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("[xayef]|(GH)").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Str("a".into()), ); assert_eq!( @@ -768,7 +769,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("xyz").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![]) ); @@ -777,7 +778,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("xyz").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Null ); } @@ -897,11 +898,11 @@ fn test_prepend_append() { DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), DataValue::Null, ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Null, DataValue::from(1), - DataValue::from(2) + DataValue::from(2), ]), ); assert_eq!( @@ -909,7 +910,7 @@ fn test_prepend_append() { DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), DataValue::Null, ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::from(1), DataValue::from(2), @@ -950,9 +951,9 @@ fn test_sort_reverse() { DataValue::from(2.0), DataValue::from(1), DataValue::from(2), - DataValue::Null + DataValue::Null, ])]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Null, DataValue::from(1), @@ -965,9 +966,9 @@ fn test_sort_reverse() { DataValue::from(2.0), DataValue::from(1), DataValue::from(2), - DataValue::Null + DataValue::Null, ])]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Null, DataValue::from(2), @@ -985,9 +986,9 @@ fn test_haversine() { DataValue::from(0), DataValue::from(180), ]) - .unwrap() - .get_float() - .unwrap(); + .unwrap() + .get_float() + .unwrap(); assert!(d.abs_diff_eq(&f64::PI(), 1e-5)); let d = op_haversine_deg_input(&[ @@ -996,9 +997,9 @@ fn test_haversine() { DataValue::from(0), DataValue::from(123), ]) - .unwrap() - .get_float() - .unwrap(); + .unwrap() + .get_float() + .unwrap(); assert!(d.abs_diff_eq(&(f64::PI() / 2.), 1e-5)); let d = op_haversine(&[ @@ -1007,9 +1008,9 @@ fn test_haversine() { DataValue::from(0), DataValue::from(f64::PI()), ]) - .unwrap() - .get_float() - .unwrap(); + .unwrap() + .get_float() + .unwrap(); assert!(d.abs_diff_eq(&f64::PI(), 1e-5)); } @@ -1038,17 +1039,17 @@ fn test_first_last() { assert_eq!( op_first(&[DataValue::List(vec![ DataValue::from(1), - DataValue::from(2) + DataValue::from(2), ])]) - .unwrap(), + .unwrap(), DataValue::from(1), ); assert_eq!( op_last(&[DataValue::List(vec![ DataValue::from(1), - DataValue::from(2) + DataValue::from(2), ])]) - .unwrap(), + .unwrap(), DataValue::from(2), ); } @@ -1066,10 +1067,10 @@ fn test_chunks() { ]), DataValue::from(2), ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ - DataValue::List(vec![DataValue::from(1), DataValue::from(2),]), - DataValue::List(vec![DataValue::from(3), DataValue::from(4),]), + DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), + DataValue::List(vec![DataValue::from(3), DataValue::from(4)]), DataValue::List(vec![DataValue::from(5)]), ]) ); @@ -1084,10 +1085,10 @@ fn test_chunks() { ]), DataValue::from(2), ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ - DataValue::List(vec![DataValue::from(1), DataValue::from(2),]), - DataValue::List(vec![DataValue::from(3), DataValue::from(4),]), + DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), + DataValue::List(vec![DataValue::from(3), DataValue::from(4)]), ]) ); assert_eq!( @@ -1101,22 +1102,22 @@ fn test_chunks() { ]), DataValue::from(3), ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::List(vec![ DataValue::from(1), DataValue::from(2), - DataValue::from(3) + DataValue::from(3), ]), DataValue::List(vec![ DataValue::from(2), DataValue::from(3), - DataValue::from(4) + DataValue::from(4), ]), DataValue::List(vec![ DataValue::from(3), DataValue::from(4), - DataValue::from(5) + DataValue::from(5), ]), ]) ) @@ -1130,11 +1131,11 @@ fn test_get() { DataValue::List(vec![ DataValue::from(1), DataValue::from(2), - DataValue::from(3) + DataValue::from(3), ]), DataValue::from(1) ]) - .unwrap(), + .unwrap(), DataValue::from(2) ); assert_eq!( @@ -1146,11 +1147,11 @@ fn test_get() { DataValue::List(vec![ DataValue::from(1), DataValue::from(2), - DataValue::from(3) + DataValue::from(3), ]), DataValue::from(1) ]) - .unwrap(), + .unwrap(), DataValue::from(2) ); } @@ -1161,24 +1162,24 @@ fn test_slice() { DataValue::List(vec![ DataValue::from(1), DataValue::from(2), - DataValue::from(3) + DataValue::from(3), ]), DataValue::from(1), DataValue::from(4) ]) - .is_err()); + .is_err()); assert_eq!( op_slice(&[ DataValue::List(vec![ DataValue::from(1), DataValue::from(2), - DataValue::from(3) + DataValue::from(3), ]), DataValue::from(1), DataValue::from(-1) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![DataValue::from(2)]) ); } @@ -1276,11 +1277,11 @@ fn test_rand() { fn test_set_ops() { assert_eq!( op_union(&[ - DataValue::List([1, 2, 3].into_iter().map(DataValue::from).collect(),), - DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect(),), - DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect(),) + DataValue::List([1, 2, 3].into_iter().map(DataValue::from).collect()), + DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect()), + DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect()) ]) - .unwrap(), + .unwrap(), DataValue::List([1, 2, 3, 4, 5].into_iter().map(DataValue::from).collect()) ); assert_eq!( @@ -1291,10 +1292,10 @@ fn test_set_ops() { .map(DataValue::from) .collect(), ), - DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect(),), - DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect(),) + DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect()), + DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect()) ]) - .unwrap(), + .unwrap(), DataValue::List([3, 4].into_iter().map(DataValue::from).collect()) ); assert_eq!( @@ -1305,10 +1306,20 @@ fn test_set_ops() { .map(DataValue::from) .collect(), ), - DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect(),), - DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect(),) + DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect()), + DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect()) ]) - .unwrap(), + .unwrap(), DataValue::List([1, 6].into_iter().map(DataValue::from).collect()) ); } + +#[test] +fn test_uuid() { + let v1 = op_rand_uuid_v1(&[]).unwrap(); + let v4 = op_rand_uuid_v4(&[]).unwrap(); + assert!(op_is_uuid(&[v4]).unwrap().get_bool().unwrap()); + assert!(op_uuid_timestamp(&[v1]).unwrap().get_int().is_some()); + assert!(op_to_uuid(&[DataValue::Str(SmartString::from(""))]).is_err()); + assert!(op_to_uuid(&[DataValue::Str(SmartString::from("f3b4958c-52a1-11e7-802a-010203040506"))]).is_ok()); +} \ No newline at end of file diff --git a/src/data/value.rs b/src/data/value.rs index e307338f..973c6af7 100644 --- a/src/data/value.rs +++ b/src/data/value.rs @@ -9,11 +9,34 @@ use rmp_serde::Serializer; use serde::{Deserialize, Deserializer, Serialize}; use smallvec::SmallVec; use smartstring::{LazyCompact, SmartString}; +use uuid::Uuid; use crate::data::encode::EncodedVec; use crate::data::id::{EntityId, TxId}; use crate::data::triple::StoreOp; +#[derive(Clone, Hash, Eq, PartialEq, serde_derive::Deserialize, serde_derive::Serialize)] +pub(crate) struct UuidWrapper(pub(crate) Uuid); + +impl UuidWrapper { + pub(crate) fn to_100_nanos(&self) -> Option { + self.0.get_timestamp().map(|t| t.to_unix_nanos()) + } +} + +impl PartialOrd for UuidWrapper { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for UuidWrapper { + fn cmp(&self, other: &Self) -> Ordering { + self.to_100_nanos().cmp(&other.to_100_nanos()).then_with(|| + self.0.as_bytes().cmp(other.0.as_bytes())) + } +} + #[derive(Clone)] pub(crate) struct RegexWrapper(pub(crate) Regex); @@ -25,8 +48,8 @@ impl Hash for RegexWrapper { impl Serialize for RegexWrapper { fn serialize(&self, _serializer: S) -> std::result::Result - where - S: serde::Serializer, + where + S: serde::Serializer, { panic!("serializing regex"); } @@ -34,8 +57,8 @@ impl Serialize for RegexWrapper { impl<'de> Deserialize<'de> for RegexWrapper { fn deserialize(_deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, + where + D: Deserializer<'de>, { panic!("deserializing regex"); } @@ -62,7 +85,7 @@ impl PartialOrd for RegexWrapper { } #[derive( - Clone, PartialEq, Eq, PartialOrd, Ord, serde_derive::Deserialize, serde_derive::Serialize, Hash, +Clone, PartialEq, Eq, PartialOrd, Ord, serde_derive::Deserialize, serde_derive::Serialize, Hash, )] pub(crate) enum DataValue { #[serde(rename = "0", alias = "Null")] @@ -75,6 +98,8 @@ pub(crate) enum DataValue { Str(SmartString), #[serde(rename = "X", alias = "Bytes", with = "serde_bytes")] Bytes(Vec), + #[serde(rename = "U", alias = "Uuid")] + Uuid(UuidWrapper), #[serde(rename = "R", alias = "Regex")] Regex(RegexWrapper), #[serde(rename = "L", alias = "List")] @@ -240,6 +265,10 @@ impl Debug for DataValue { DataValue::Guard => { write!(f, "guard") } + DataValue::Uuid(u) => { + let encoded = base64::encode_config(u.0.as_bytes(), base64::URL_SAFE_NO_PAD); + write!(f, "{}", encoded) + } } } } @@ -297,6 +326,15 @@ impl DataValue { _ => None, } } + pub(crate) fn get_bool(&self) -> Option { + match self { + DataValue::Bool(b) => Some(*b), + _ => None + } + } + pub(crate) fn uuid(uuid: uuid::Uuid) -> Self { + Self::Uuid(UuidWrapper(uuid)) + } } pub(crate) const LARGEST_UTF_CHAR: char = '\u{10ffff}';