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 @@
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -73,7 +73,7 @@
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true"
}
}]]>
-
+
@@ -101,6 +101,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -118,11 +152,15 @@
+
+
+
+
@@ -138,7 +176,7 @@
1663161524540
-
+
1663161616722
@@ -175,7 +213,14 @@
1663220906043
-
+
+ 1663233714452
+
+
+
+ 1663233714452
+
+
@@ -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}';