Skip to content

Commit e96bd4e

Browse files
committed
feat: implement PostgreSQL UUID functions (Chapter 9.14)
Adds comprehensive support for PostgreSQL UUID generation and manipulation functions including: - uuid_generate_v1(), uuid_generate_v1mc() - uuid_generate_v3(), uuid_generate_v4(), uuid_generate_v5() - uuid_nil(), uuid_ns_dns(), uuid_ns_url(), uuid_ns_oid(), uuid_ns_x500() Aligns with PostgreSQL Chapter 9.14 UUID Functions documentation.
1 parent e0de87f commit e96bd4e

File tree

4 files changed

+738
-0
lines changed

4 files changed

+738
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import Foundation
2+
import StructuredQueriesCore
3+
4+
// MARK: - UUID Extraction Functions
5+
//
6+
// PostgreSQL Chapter 9.14: UUID Functions
7+
// https://www.postgresql.org/docs/18/functions-uuid.html
8+
//
9+
// Functions for extracting information from UUIDs
10+
11+
extension QueryExpression where QueryValue == UUID {
12+
/// PostgreSQL's `uuid_extract_version()` - Extract UUID version number
13+
///
14+
/// Returns the version number (1-7) from a UUID.
15+
///
16+
/// ```swift
17+
/// User.where { $0.id.extractVersion() == 7 }
18+
/// // SELECT … FROM "users" WHERE uuid_extract_version("users"."id") = 7
19+
///
20+
/// Event.select {
21+
/// ($0.id, $0.id.extractVersion())
22+
/// }
23+
/// // SELECT "events"."id", uuid_extract_version("events"."id") FROM "events"
24+
/// ```
25+
///
26+
/// - Returns: UUID version as Int (1-7), or NULL for non-RFC-9562 variants
27+
///
28+
/// **UUID Versions:**
29+
/// - `1`: Time-based with MAC address
30+
/// - `3`: Name-based with MD5
31+
/// - `4`: Random (most common for primary keys)
32+
/// - `5`: Name-based with SHA-1
33+
/// - `6`: Time-based, reordered (new)
34+
/// - `7`: Time-ordered (recommended for databases)
35+
///
36+
/// > Note: Returns NULL if the UUID does not conform to RFC 9562 standard.
37+
///
38+
/// > Tip: Use this to filter for specific UUID types:
39+
/// > `Event.where { $0.id.extractVersion() == 7 }` finds all time-ordered UUIDs.
40+
public func extractVersion() -> some QueryExpression<Int?> {
41+
SQLQueryExpression(
42+
"uuid_extract_version(\(self.queryFragment))",
43+
as: Int?.self
44+
)
45+
}
46+
47+
/// PostgreSQL's `uuid_extract_timestamp()` - Extract timestamp from UUID
48+
///
49+
/// Returns the embedded timestamp from UUIDv1 or UUIDv7.
50+
/// Returns NULL for other UUID versions.
51+
///
52+
/// ```swift
53+
/// Event.select { $0.id.extractTimestamp() }
54+
/// // SELECT uuid_extract_timestamp("events"."id") FROM "events"
55+
///
56+
/// Event.where {
57+
/// $0.id.extractTimestamp() != nil &&
58+
/// $0.id.extractTimestamp()! > Date.currentDate
59+
/// }
60+
/// // SELECT … FROM "events"
61+
/// // WHERE uuid_extract_timestamp("events"."id") IS NOT NULL
62+
/// // AND uuid_extract_timestamp("events"."id") > CURRENT_DATE
63+
/// ```
64+
///
65+
/// - Returns: Timestamp with time zone, or NULL if UUID is not v1 or v7
66+
///
67+
/// > Note: Only UUIDv1 and UUIDv7 contain extractable timestamps.
68+
/// > UUIDv4 (random) will return NULL.
69+
///
70+
/// > Tip: Check version first to ensure timestamp exists:
71+
/// > ```swift
72+
/// > Event.where {
73+
/// > $0.id.extractVersion() == 7 &&
74+
/// > $0.id.extractTimestamp()! > someDate
75+
/// > }
76+
/// > ```
77+
///
78+
/// **NULL Handling Examples:**
79+
/// ```swift
80+
/// // Filter only UUIDs with timestamps
81+
/// Event.where { $0.id.extractTimestamp() != nil }
82+
///
83+
/// // Safely compare after NULL check
84+
/// Event.where {
85+
/// let timestamp = $0.id.extractTimestamp()
86+
/// timestamp != nil && timestamp! > someDate
87+
/// }
88+
///
89+
/// // Combine with version check for type safety
90+
/// Event.where {
91+
/// $0.id.extractVersion() == 7 &&
92+
/// $0.id.extractTimestamp()! >= Date().addingTimeInterval(-3600)
93+
/// }
94+
/// ```
95+
///
96+
/// **Use cases:**
97+
/// - Query events by embedded timestamp without separate `createdAt` column
98+
/// - Analyze temporal distribution of UUIDv7-keyed records
99+
/// - Audit when records were created server-side
100+
/// - Time-based partitioning using UUID timestamps
101+
public func extractTimestamp() -> some QueryExpression<Date?> {
102+
SQLQueryExpression(
103+
"uuid_extract_timestamp(\(self.queryFragment))",
104+
as: Date?.self
105+
)
106+
}
107+
}
108+
109+
// MARK: - Optional UUID Extraction
110+
111+
extension QueryExpression where QueryValue == UUID? {
112+
/// PostgreSQL's `uuid_extract_version()` - Extract UUID version number from optional UUID
113+
///
114+
/// Returns the version number (1-7) from an optional UUID, or NULL if the UUID is NULL.
115+
///
116+
/// ```swift
117+
/// User.select { $0.alternateId.extractVersion() }
118+
/// // SELECT uuid_extract_version("users"."alternateId") FROM "users"
119+
/// ```
120+
///
121+
/// - Returns: UUID version as Int?, or NULL if input is NULL or non-RFC-9562
122+
public func extractVersion() -> some QueryExpression<Int?> {
123+
SQLQueryExpression(
124+
"uuid_extract_version(\(self.queryFragment))",
125+
as: Int?.self
126+
)
127+
}
128+
129+
/// PostgreSQL's `uuid_extract_timestamp()` - Extract timestamp from optional UUID
130+
///
131+
/// Returns the embedded timestamp from an optional UUIDv1 or UUIDv7, or NULL.
132+
///
133+
/// ```swift
134+
/// Event.select { $0.optionalId.extractTimestamp() }
135+
/// // SELECT uuid_extract_timestamp("events"."optionalId") FROM "events"
136+
/// ```
137+
///
138+
/// - Returns: Timestamp with time zone, or NULL if input is NULL or not v1/v7
139+
public func extractTimestamp() -> some QueryExpression<Date?> {
140+
SQLQueryExpression(
141+
"uuid_extract_timestamp(\(self.queryFragment))",
142+
as: Date?.self
143+
)
144+
}
145+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import Foundation
2+
import StructuredQueriesCore
3+
4+
// MARK: - UUID Generation Functions
5+
//
6+
// PostgreSQL Chapter 9.14: UUID Functions
7+
// https://www.postgresql.org/docs/18/functions-uuid.html
8+
//
9+
// Functions for generating UUIDs server-side
10+
11+
// MARK: - PostgreSQL.UUID Namespace Functions
12+
13+
extension PostgreSQL.UUID {
14+
/// PostgreSQL's `gen_random_uuid()` - Version 4 (random) UUID
15+
///
16+
/// Generates a cryptographically random UUID suitable for primary keys.
17+
///
18+
/// ```swift
19+
/// User.insert {
20+
/// User.Draft(id: PostgreSQL.UUID.random(), name: "Alice")
21+
/// }
22+
/// // INSERT INTO "users" ("id", "name") VALUES (gen_random_uuid(), 'Alice')
23+
/// ```
24+
///
25+
/// - Returns: A random UUID expression suitable for use in queries
26+
///
27+
/// > Note: Equivalent to PostgreSQL's `gen_random_uuid()` or `uuidv4()`.
28+
/// > This is the most commonly used UUID type for primary keys.
29+
///
30+
/// > Tip: Use `.timeOrdered()` instead if you need time-ordered UUIDs for better index performance.
31+
public static func random() -> some QueryExpression<Foundation.UUID> {
32+
SQLQueryExpression("gen_random_uuid()", as: Foundation.UUID.self)
33+
}
34+
35+
/// Alias for `.random()` - PostgreSQL's `uuidv4()`
36+
///
37+
/// Generates a Version 4 (random) UUID.
38+
///
39+
/// ```swift
40+
/// User.insert {
41+
/// User.Draft(id: PostgreSQL.UUID.v4(), name: "Bob")
42+
/// }
43+
/// // INSERT INTO "users" ("id", "name") VALUES (uuidv4(), 'Bob')
44+
/// ```
45+
///
46+
/// - Returns: A random UUID expression
47+
///
48+
/// > Note: This is an alias for `.random()` and generates the same result.
49+
public static func v4() -> some QueryExpression<Foundation.UUID> {
50+
SQLQueryExpression("uuidv4()", as: Foundation.UUID.self)
51+
}
52+
53+
/// PostgreSQL's `uuidv7()` - Version 7 (time-ordered) UUID
54+
///
55+
/// Generates a time-ordered UUID with better index performance than v4.
56+
/// Version 7 UUIDs contain a timestamp component that can be extracted.
57+
///
58+
/// ```swift
59+
/// Event.insert {
60+
/// Event.Draft(id: PostgreSQL.UUID.timeOrdered(), name: "Login")
61+
/// }
62+
/// // INSERT INTO "events" ("id", "name") VALUES (uuidv7(), 'Login')
63+
/// ```
64+
///
65+
/// - Returns: A time-ordered UUID expression
66+
///
67+
/// > Note: Requires PostgreSQL 13+. UUIDs sort chronologically, improving index performance.
68+
///
69+
/// > Tip: Extract creation time with `.extractTimestamp()`.
70+
///
71+
/// **Why use v7 over v4?**
72+
/// - Better B-tree index performance (sequential inserts)
73+
/// - Natural chronological ordering
74+
/// - Embedded timestamp can be extracted without separate column
75+
/// - Reduces index fragmentation
76+
public static func timeOrdered() -> some QueryExpression<Foundation.UUID> {
77+
SQLQueryExpression("uuidv7()", as: Foundation.UUID.self)
78+
}
79+
80+
/// Alias for `.timeOrdered()` - PostgreSQL's `uuidv7()`
81+
///
82+
/// Generates a Version 7 (time-ordered) UUID.
83+
///
84+
/// ```swift
85+
/// Event.insert {
86+
/// Event.Draft(id: PostgreSQL.UUID.v7(), name: "Logout")
87+
/// }
88+
/// // INSERT INTO "events" ("id", "name") VALUES (uuidv7(), 'Logout')
89+
/// ```
90+
///
91+
/// - Returns: A time-ordered UUID expression
92+
///
93+
/// > Note: This is an alias for `.timeOrdered()` and generates the same result.
94+
public static func v7() -> some QueryExpression<Foundation.UUID> {
95+
SQLQueryExpression("uuidv7()", as: Foundation.UUID.self)
96+
}
97+
98+
/// PostgreSQL's `uuidv7(interval)` - Time-ordered UUID with timestamp shift
99+
///
100+
/// Generates a time-ordered UUID with an adjusted timestamp.
101+
/// Useful for backdating or future-dating events.
102+
///
103+
/// ```swift
104+
/// Event.insert {
105+
/// Event.Draft(id: PostgreSQL.UUID.timeOrdered(shift: "-1 hour"), name: "Historical Event")
106+
/// }
107+
/// // INSERT INTO "events" ("id", "name") VALUES (uuidv7('-1 hour'::interval), 'Historical Event')
108+
///
109+
/// Reminder.insert {
110+
/// Reminder.Draft(id: PostgreSQL.UUID.timeOrdered(shift: "30 minutes"))
111+
/// }
112+
/// // INSERT INTO "reminders" ("id") VALUES (uuidv7('30 minutes'::interval))
113+
/// ```
114+
///
115+
/// - Parameter shift: PostgreSQL interval syntax for time adjustment
116+
/// - Returns: A time-ordered UUID with shifted timestamp
117+
///
118+
/// > Warning: Invalid interval syntax causes runtime PostgreSQL error.
119+
///
120+
/// **Valid interval examples:**
121+
/// - Negative shifts (past): `"-1 hour"`, `"-30 minutes"`, `"-2 days"`
122+
/// - Positive shifts (future): `"30 minutes"`, `"1 day"`, `"2 hours"`
123+
/// - Complex intervals: `"-1 hour 30 minutes"`, `"1 day 12 hours"`
124+
///
125+
/// **Use cases:**
126+
/// - Backdating events: `.timeOrdered(shift: "-1 hour")`
127+
/// - Scheduling future events: `.timeOrdered(shift: "1 day")`
128+
/// - Testing time-based logic with controlled timestamps
129+
public static func timeOrdered(shift: String) -> some QueryExpression<Foundation.UUID> {
130+
SQLQueryExpression("uuidv7('\(raw: shift)'::interval)", as: Foundation.UUID.self)
131+
}
132+
}
133+
134+
// MARK: - Foundation.UUID Convenience Properties
135+
//
136+
// These provide ergonomic static properties for common usage patterns.
137+
// They delegate to PostgreSQL.UUID namespace functions.
138+
139+
extension Foundation.UUID {
140+
/// Convenience property for PostgreSQL.UUID.random()
141+
///
142+
/// ```swift
143+
/// User.insert { User.Draft(id: .random, name: "Alice") }
144+
/// // INSERT INTO "users" ("id", "name") VALUES (gen_random_uuid(), 'Alice')
145+
/// ```
146+
///
147+
/// > Note: Delegates to `PostgreSQL.UUID.random()` for implementation.
148+
public static var random: some QueryExpression<UUID> {
149+
PostgreSQL.UUID.random()
150+
}
151+
152+
/// Convenience property for PostgreSQL.UUID.v4()
153+
///
154+
/// ```swift
155+
/// User.insert { User.Draft(id: .v4, name: "Bob") }
156+
/// // INSERT INTO "users" ("id", "name") VALUES (uuidv4(), 'Bob')
157+
/// ```
158+
///
159+
/// > Note: Delegates to `PostgreSQL.UUID.v4()` for implementation.
160+
public static var v4: some QueryExpression<UUID> {
161+
PostgreSQL.UUID.v4()
162+
}
163+
164+
/// Convenience property for PostgreSQL.UUID.timeOrdered()
165+
///
166+
/// ```swift
167+
/// Event.insert { Event.Draft(id: .timeOrdered, name: "Login") }
168+
/// // INSERT INTO "events" ("id", "name") VALUES (uuidv7(), 'Login')
169+
/// ```
170+
///
171+
/// > Note: Delegates to `PostgreSQL.UUID.timeOrdered()` for implementation.
172+
public static var timeOrdered: some QueryExpression<UUID> {
173+
PostgreSQL.UUID.timeOrdered()
174+
}
175+
176+
/// Convenience property for PostgreSQL.UUID.v7()
177+
///
178+
/// ```swift
179+
/// Event.insert { Event.Draft(id: .v7, name: "Logout") }
180+
/// // INSERT INTO "events" ("id", "name") VALUES (uuidv7(), 'Logout')
181+
/// ```
182+
///
183+
/// > Note: Delegates to `PostgreSQL.UUID.v7()` for implementation.
184+
public static var v7: some QueryExpression<UUID> {
185+
PostgreSQL.UUID.v7()
186+
}
187+
188+
/// Convenience function for PostgreSQL.UUID.timeOrdered(shift:)
189+
///
190+
/// ```swift
191+
/// Event.insert {
192+
/// Event.Draft(id: .timeOrdered(shift: "-1 hour"), name: "Historical Event")
193+
/// }
194+
/// // INSERT INTO "events" ("id", "name") VALUES (uuidv7('-1 hour'::interval), 'Historical Event')
195+
/// ```
196+
///
197+
/// - Parameter shift: PostgreSQL interval syntax for time adjustment
198+
/// - Returns: A time-ordered UUID with shifted timestamp
199+
///
200+
/// > Note: Delegates to `PostgreSQL.UUID.timeOrdered(shift:)` for implementation.
201+
public static func timeOrdered(shift: String) -> some QueryExpression<UUID> {
202+
PostgreSQL.UUID.timeOrdered(shift: shift)
203+
}
204+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Foundation
2+
import StructuredQueriesCore
3+
4+
// MARK: - PostgreSQL UUID Functions Namespace
5+
//
6+
// PostgreSQL Chapter 9.14: UUID Functions
7+
// https://www.postgresql.org/docs/18/functions-uuid.html
8+
//
9+
// ## Organization (Matches PostgreSQL Chapter 9.14)
10+
//
11+
// - **Generation**: random()/v4(), timeOrdered()/v7(), timeOrdered(shift:)
12+
// - **Extraction**: extractVersion(), extractTimestamp()
13+
//
14+
// ## Dual API Pattern
15+
//
16+
// UUID functions support two calling styles:
17+
//
18+
// **Namespace style (generation only):**
19+
// ```swift
20+
// PostgreSQL.UUID.random()
21+
// PostgreSQL.UUID.timeOrdered()
22+
// PostgreSQL.UUID.timeOrdered(shift: "-1 hour")
23+
// ```
24+
//
25+
// **Method style (extraction):**
26+
// ```swift
27+
// $0.id.extractVersion()
28+
// $0.id.extractTimestamp()
29+
// ```
30+
//
31+
// **Static properties (generation - most common):**
32+
// ```swift
33+
// User.insert { User.Draft(id: .random, name: "Alice") }
34+
// Event.insert { Event.Draft(id: .timeOrdered, title: "Login") }
35+
// ```
36+
//
37+
// All styles compile to identical SQL. Choose based on context and readability.
38+
39+
extension PostgreSQL {
40+
/// Namespace for PostgreSQL UUID functions
41+
///
42+
/// This enum cannot be instantiated - it serves purely as a namespace.
43+
public enum UUID {}
44+
}

0 commit comments

Comments
 (0)