From 70c682987c802a8c77be7ae896fc576ee2f10c33 Mon Sep 17 00:00:00 2001 From: Micha de Vries Date: Fri, 19 Jul 2024 13:11:55 +0200 Subject: [PATCH] Allow generating ulid and uuid based on a timestamp (#4384) --- core/src/fnc/rand.rs | 27 +++++++++--- core/src/sql/uuid.rs | 11 +++++ lib/tests/function.rs | 99 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/core/src/fnc/rand.rs b/core/src/fnc/rand.rs index 56b8c338..5e2d7456 100644 --- a/core/src/fnc/rand.rs +++ b/core/src/fnc/rand.rs @@ -2,6 +2,7 @@ use crate::cnf::ID_CHARS; use crate::err::Error; use crate::sql::uuid::Uuid; use crate::sql::value::Value; +use crate::sql::Datetime; use chrono::{TimeZone, Utc}; use nanoid::nanoid; use rand::distributions::{Alphanumeric, DistString}; @@ -150,12 +151,21 @@ pub fn time((range,): (Option<(i64, i64)>,)) -> Result { Ok(Utc.timestamp_opt(val, 0).earliest().unwrap().into()) } -pub fn ulid(_: ()) -> Result { - Ok(Ulid::new().to_string().into()) +pub fn ulid((timestamp,): (Option,)) -> Result { + let ulid = match timestamp { + Some(timestamp) => Ulid::from_datetime(timestamp.0.into()), + None => Ulid::new(), + }; + + Ok(ulid.to_string().into()) } -pub fn uuid(_: ()) -> Result { - Ok(Uuid::new().into()) +pub fn uuid((timestamp,): (Option,)) -> Result { + let uuid = match timestamp { + Some(timestamp) => Uuid::new_v7_from_datetime(timestamp), + None => Uuid::new(), + }; + Ok(uuid.into()) } pub mod uuid { @@ -163,12 +173,17 @@ pub mod uuid { use crate::err::Error; use crate::sql::uuid::Uuid; use crate::sql::value::Value; + use crate::sql::Datetime; pub fn v4(_: ()) -> Result { Ok(Uuid::new_v4().into()) } - pub fn v7(_: ()) -> Result { - Ok(Uuid::new_v7().into()) + pub fn v7((timestamp,): (Option,)) -> Result { + let uuid = match timestamp { + Some(timestamp) => Uuid::new_v7_from_datetime(timestamp), + None => Uuid::new(), + }; + Ok(uuid.into()) } } diff --git a/core/src/sql/uuid.rs b/core/src/sql/uuid.rs index 82ba3444..8a38f594 100644 --- a/core/src/sql/uuid.rs +++ b/core/src/sql/uuid.rs @@ -6,6 +6,8 @@ use std::ops::Deref; use std::str; use std::str::FromStr; +use super::Datetime; + pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Uuid"; #[revisioned(revision = 1)] @@ -78,6 +80,15 @@ impl Uuid { pub fn new_v7() -> Self { Self(uuid::Uuid::now_v7()) } + /// Generate a new V7 UUID + pub fn new_v7_from_datetime(timestamp: Datetime) -> Self { + let ts = uuid::Timestamp::from_unix( + uuid::NoContext, + timestamp.0.timestamp() as u64, + timestamp.0.timestamp_subsec_nanos(), + ); + Self(uuid::Uuid::new_v7(ts)) + } /// Convert the Uuid to a raw String pub fn to_raw(&self) -> String { self.0.to_string() diff --git a/lib/tests/function.rs b/lib/tests/function.rs index 0716ceb2..bb7d683f 100644 --- a/lib/tests/function.rs +++ b/lib/tests/function.rs @@ -2953,6 +2953,39 @@ async fn function_rand_ulid() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn function_rand_ulid_from_datetime() -> Result<(), Error> { + let sql = r#" + CREATE ONLY test:[rand::ulid()] SET created = time::now(), num = 1; + SLEEP 1ms; + LET $rec = CREATE ONLY test:[rand::ulid()] SET created = time::now(), num = 2; + SLEEP 1ms; + CREATE ONLY test:[rand::ulid()] SET created = time::now(), num = 3; + SELECT VALUE num FROM test:[rand::ulid($rec.created - 1ms)]..; + "#; + let mut test = Test::new(sql).await?; + // + let tmp = test.next()?.result?; + assert!(tmp.is_object()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_object()); + // + let tmp = test.next()?.result?; + assert_eq!(tmp, Value::parse("[2, 3]")); + // + Ok(()) +} + #[tokio::test] async fn function_rand_uuid() -> Result<(), Error> { let sql = r#" @@ -2966,6 +2999,39 @@ async fn function_rand_uuid() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn function_rand_uuid_from_datetime() -> Result<(), Error> { + let sql = r#" + CREATE ONLY test:[rand::uuid()] SET created = time::now(), num = 1; + SLEEP 1ms; + LET $rec = CREATE ONLY test:[rand::uuid()] SET created = time::now(), num = 2; + SLEEP 1ms; + CREATE ONLY test:[rand::uuid()] SET created = time::now(), num = 3; + SELECT VALUE num FROM test:[rand::uuid($rec.created - 1ms)]..; + "#; + let mut test = Test::new(sql).await?; + // + let tmp = test.next()?.result?; + assert!(tmp.is_object()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_object()); + // + let tmp = test.next()?.result?; + assert_eq!(tmp, Value::parse("[2, 3]")); + // + Ok(()) +} + #[tokio::test] async fn function_rand_uuid_v4() -> Result<(), Error> { let sql = r#" @@ -2992,6 +3058,39 @@ async fn function_rand_uuid_v7() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn function_rand_uuid_v7_from_datetime() -> Result<(), Error> { + let sql = r#" + CREATE ONLY test:[rand::uuid::v7()] SET created = time::now(), num = 1; + SLEEP 1ms; + LET $rec = CREATE ONLY test:[rand::uuid::v7()] SET created = time::now(), num = 2; + SLEEP 1ms; + CREATE ONLY test:[rand::uuid::v7()] SET created = time::now(), num = 3; + SELECT VALUE num FROM test:[rand::uuid::v7($rec.created - 1ms)]..; + "#; + let mut test = Test::new(sql).await?; + // + let tmp = test.next()?.result?; + assert!(tmp.is_object()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_none()); + // + let tmp = test.next()?.result?; + assert!(tmp.is_object()); + // + let tmp = test.next()?.result?; + assert_eq!(tmp, Value::parse("[2, 3]")); + // + Ok(()) +} + // -------------------------------------------------- // string // --------------------------------------------------