1
- use pgrx:: * ;
2
- use uuid7:: uuid7;
1
+ use std:: io:: { Error as IoError , ErrorKind } ;
2
+ use std:: str:: FromStr ;
3
+
4
+ use chrono:: NaiveDateTime ;
5
+ use pgrx:: pg_extern;
6
+ use uuid:: Uuid ;
7
+
8
+ use crate :: common:: { naive_datetime_to_pg_timestamptz, OrPgrxError } ;
9
+
10
+ /// Generate a new UUIDv6
11
+ fn new_uuidv7 ( ) -> Uuid {
12
+ Uuid :: now_v7 ( )
13
+ }
3
14
4
15
/// Generate a UUID v7
5
16
#[ pg_extern]
6
17
fn idkit_uuidv7_generate ( ) -> String {
7
- uuid7 ( ) . to_string ( )
18
+ new_uuidv7 ( ) . as_hyphenated ( ) . to_string ( )
8
19
}
9
20
10
21
/// Generate a UUID v7, producing a Postgres text object
@@ -16,18 +27,51 @@ fn idkit_uuidv7_generate_text() -> String {
16
27
/// Generate a UUID v7, producing a Postgres uuid object
17
28
#[ pg_extern]
18
29
fn idkit_uuidv7_generate_uuid ( ) -> pgrx:: Uuid {
19
- pgrx:: Uuid :: from_slice ( uuid7 ( ) . as_bytes ( ) )
20
- . unwrap_or_else ( |e| error ! ( "{}" , format!( "failed to generate/parse uuidv7: {}" , e) ) )
30
+ pgrx:: Uuid :: from_slice ( new_uuidv7 ( ) . as_bytes ( ) )
31
+ . map_err ( |e| IoError :: new ( ErrorKind :: Other , format ! ( "{e:?}" ) ) )
32
+ . or_pgrx_error ( "failed to convert UUIDv7 to Postgres uuid type" )
33
+ }
34
+
35
+ /// Retrieve a `timestamptz` (with millisecond precision) from a given textual UUIDv7
36
+ ///
37
+ /// # Panics
38
+ ///
39
+ /// This function panics (with a [`pgrx::error`]) when the timezone can't be created
40
+ #[ pg_extern]
41
+ fn idkit_uuidv7_extract_timestamptz ( val : String ) -> pgrx:: TimestampWithTimeZone {
42
+ let ( secs, nanos) = Uuid :: from_str ( val. as_str ( ) )
43
+ . or_pgrx_error ( format ! ( "[{val}] is an invalid UUIDv7" ) )
44
+ . get_timestamp ( )
45
+ . or_pgrx_error ( "failed to extract timestamp" )
46
+ . to_unix ( ) ;
47
+ if secs > i64:: MAX as u64 {
48
+ pgrx:: error!(
49
+ "value [{secs}] seconds is larger than the max signed 64bit integer [{}]" ,
50
+ i64 :: MAX
51
+ ) ;
52
+ }
53
+ naive_datetime_to_pg_timestamptz (
54
+ NaiveDateTime :: from_timestamp_opt ( secs as i64 , nanos)
55
+ . or_pgrx_error ( "failed to create timestamp from UUIDV7 [{val}]" )
56
+ . and_utc ( ) ,
57
+ format ! ( "failed to convert timestamp for UUIDV7 [{val}]" ) ,
58
+ )
21
59
}
22
60
23
61
//////////
24
62
// Test //
25
63
//////////
26
64
27
65
#[ cfg( any( test, feature = "pg_test" ) ) ]
28
- #[ pg_schema]
66
+ #[ pgrx :: pg_schema]
29
67
mod tests {
30
- use pgrx:: * ;
68
+ use chrono:: { DateTime , Utc } ;
69
+ use pgrx:: datum:: datetime_support:: ToIsoString ;
70
+ use pgrx:: pg_test;
71
+
72
+ use crate :: uuid_v7:: idkit_uuidv7_extract_timestamptz;
73
+ use crate :: uuid_v7:: idkit_uuidv7_generate;
74
+ use crate :: uuid_v7:: idkit_uuidv7_generate_uuid;
31
75
32
76
/// Basic length test
33
77
#[ pg_test]
@@ -36,6 +80,12 @@ mod tests {
36
80
assert_eq ! ( generated. len( ) , 36 ) ;
37
81
}
38
82
83
+ /// Basic length test for bytes
84
+ #[ pg_test]
85
+ fn test_uuidv7_len_uuid ( ) {
86
+ assert_eq ! ( idkit_uuidv7_generate_uuid( ) . len( ) , 16 ) ;
87
+ }
88
+
39
89
/// Check version integer in UUID string
40
90
#[ pg_test]
41
91
fn test_uuidv7_version_int ( ) {
@@ -44,4 +94,23 @@ mod tests {
44
94
assert ! ( c9. is_some( ) ) ;
45
95
assert_eq ! ( c9. unwrap( ) , '7' ) ;
46
96
}
97
+
98
+ /// Ensure timestamps extracted from CUIDs are valid
99
+ #[ pg_test]
100
+ fn test_uuidv7_extract_timestamptz ( ) {
101
+ let timestamp = idkit_uuidv7_extract_timestamptz ( idkit_uuidv7_generate ( ) ) ;
102
+ let parsed: DateTime < Utc > = DateTime :: parse_from_rfc3339 ( & timestamp. to_iso_string ( ) )
103
+ . expect ( "extracted timestamp as ISO string parsed to UTC DateTime" )
104
+ . into ( ) ;
105
+ assert ! (
106
+ Utc :: now( ) . signed_duration_since( parsed) . num_seconds( ) < 3 ,
107
+ "extracted, printed & re-parsed uuidv7 timestamp is from recent past (within 3s)"
108
+ ) ;
109
+ }
110
+
111
+ /// Ensure an existing, hardcoded timestamp works for extraction
112
+ #[ pg_test]
113
+ fn test_uuidv7_extract_timestamptz_existing ( ) {
114
+ idkit_uuidv7_extract_timestamptz ( "016b0dd7-0cbb-691e-8548-9888e89d0527" . into ( ) ) ;
115
+ }
47
116
}
0 commit comments