1use crate::compiler::prelude::*;
2use bytes::Bytes;
3use chrono::{DateTime, Utc};
4use std::sync::LazyLock;
5use uuid::{NoContext, timestamp::Timestamp};
6
7static DEFAULT_TIMESTAMP: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("`now()`")));
8
9static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
10 vec![
11 Parameter::optional(
12 "timestamp",
13 kind::TIMESTAMP,
14 "The timestamp used to generate the UUIDv7.",
15 )
16 .default(&DEFAULT_TIMESTAMP),
17 ]
18});
19
20#[allow(clippy::cast_sign_loss)] fn uuid_v7(timestamp: Option<Value>) -> Resolved {
22 let utc_timestamp: DateTime<Utc> = if let Some(timestamp) = timestamp {
23 timestamp.try_timestamp()?
24 } else {
25 Utc::now()
26 };
27
28 let seconds = utc_timestamp.timestamp() as u64;
29 let nanoseconds = match utc_timestamp.timestamp_nanos_opt() {
30 #[allow(clippy::cast_possible_truncation)] Some(nanos) => nanos as u32,
32 None => return Err(ValueError::OutOfRange(Kind::timestamp()).into()),
33 };
34 let timestamp = Timestamp::from_unix(NoContext, seconds, nanoseconds);
35
36 let mut buffer = [0; 36];
37 let uuid = uuid::Uuid::new_v7(timestamp)
38 .hyphenated()
39 .encode_lower(&mut buffer);
40 Ok(Bytes::copy_from_slice(uuid.as_bytes()).into())
41}
42
43#[derive(Clone, Copy, Debug)]
44pub struct UuidV7;
45
46impl Function for UuidV7 {
47 fn identifier(&self) -> &'static str {
48 "uuid_v7"
49 }
50
51 fn usage(&self) -> &'static str {
52 "Generates a random [UUIDv7](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#name-uuid-version-7) string."
53 }
54
55 fn category(&self) -> &'static str {
56 Category::Random.as_ref()
57 }
58
59 fn return_kind(&self) -> u16 {
60 kind::BYTES
61 }
62
63 fn parameters(&self) -> &'static [Parameter] {
64 PARAMETERS.as_slice()
65 }
66
67 fn examples(&self) -> &'static [Example] {
68 &[
69 example! {
70 title: "Create a UUIDv7 with implicit `now()`",
71 source: "uuid_v7()",
72 result: Ok("0135ddb4-a444-794c-a7a2-088f260104c0"),
73 deterministic: false,
74 },
75 example! {
76 title: "Create a UUIDv7 with explicit `now()`",
77 source: "uuid_v7(now())",
78 result: Ok("0135ddb4-a444-794c-a7a2-088f260104c0"),
79 deterministic: false,
80 },
81 example! {
82 title: "Create a UUIDv7 with custom timestamp",
83 source: "uuid_v7(t'2020-12-30T22:20:53.824727Z')",
84 result: Ok("0176b5bd-5d19-794c-a7a2-088f260104c0"),
85 deterministic: false,
86 },
87 ]
88 }
89
90 fn compile(
91 &self,
92 _state: &state::TypeState,
93 _ctx: &mut FunctionCompileContext,
94 arguments: ArgumentList,
95 ) -> Compiled {
96 let timestamp = arguments.optional("timestamp");
97
98 Ok(UuidV7Fn { timestamp }.as_expr())
99 }
100}
101
102#[derive(Debug, Clone)]
103struct UuidV7Fn {
104 timestamp: Option<Box<dyn Expression>>,
105}
106
107impl FunctionExpression for UuidV7Fn {
108 fn resolve(&self, ctx: &mut Context) -> Resolved {
109 let timestamp = self
110 .timestamp
111 .as_ref()
112 .map(|m| m.resolve(ctx))
113 .transpose()?;
114
115 uuid_v7(timestamp)
116 }
117
118 fn type_def(&self, _: &TypeState) -> TypeDef {
119 TypeDef::bytes().infallible()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::value::Value;
127 use std::collections::BTreeMap;
128
129 test_type_def![default {
130 expr: |_| { UuidV7Fn { timestamp: None } },
131 want: TypeDef::bytes().infallible(),
132 }];
133
134 #[test]
135 fn uuid_v7() {
136 let mut state = state::RuntimeState::default();
137 let mut object: Value = Value::Object(BTreeMap::new());
138 let tz = TimeZone::default();
139 let mut ctx = Context::new(&mut object, &mut state, &tz);
140 let value = UuidV7Fn { timestamp: None }.resolve(&mut ctx).unwrap();
141
142 assert!(matches!(&value, Value::Bytes(_)));
143
144 match value {
145 Value::Bytes(val) => {
146 let val = String::from_utf8_lossy(&val);
147 uuid::Uuid::parse_str(&val).expect("valid UUID V7");
148 }
149 _ => unreachable!(),
150 }
151 }
152}