Skip to content

Commit e4332eb

Browse files
committed
feat: implement PostgreSQL UUID functions (Chapter 9.14)
Add comprehensive server-side UUID generation and extraction functions following PostgreSQL Chapter 9.14 specification. Generation Functions: • gen_random_uuid() / uuidv4() - Random UUID (v4) for primary keys • uuidv7() - Time-ordered UUID (v7) for better index performance • uuidv7(interval) - Time-shifted UUID for backdating/scheduling Extraction Functions: • uuid_extract_version() - Extract version number (1-7) • uuid_extract_timestamp() - Extract creation timestamp from v1/v7 UUIDs API Design: • Dual interface: PostgreSQL.UUID namespace + Foundation.UUID convenience properties • Ergonomic syntax: .random, .timeOrdered, .v4, .v7 • Type-safe QueryExpression return types • Proper NULL handling for optional extraction results Benefits: • Reduced network roundtrips via server-side generation • Better B-tree index performance with time-ordered UUIDs • Extract timestamps without separate createdAt columns • Full PostgreSQL 9.14 UUID coverage (6/6 functions) Tests: • 20 comprehensive test cases covering generation, extraction, composition, and edge cases • Real-world usage patterns: batch inserts, filtering, ordering • All 880 tests passing Documentation: • Complete DocC documentation with examples and warnings • Updated _COVERAGE.md with 100% Chapter 9.14 coverage • Implementation notes explaining v4 vs v7 trade-offs
1 parent 76c1498 commit e4332eb

File tree

5 files changed

+781
-0
lines changed

5 files changed

+781
-0
lines changed

Sources/StructuredQueriesPostgres/Functions/Aggregate/_COVERAGE.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,50 @@ Due to Swift's type system, each Select.aggregate method requires 5 overloads:
148148
- **Reason**: Administration/introspection, not query building
149149
- **Strategy**: Out of scope for this package
150150

151+
**9.14: UUID Functions**
152+
- **Reason**: Now fully implemented (6/6 functions)
153+
- **Status**: ✅ Complete
154+
- **Coverage**: See dedicated section below
155+
151156
### Coverage Summary
152157
- **Core SQL**: ~95% of commonly-used functions implemented
153158
- **Skipped**: Niche types, administration, and features superseded by better Swift patterns
154159
- **Philosophy**: Maximize value per line of code, wait for real-world usage to guide additions
160+
161+
## PostgreSQL Chapter 9.14: UUID Functions
162+
163+
| Function | Purpose | Status | Swift API | Notes |
164+
|----------|---------|--------|-----------|-------|
165+
| `gen_random_uuid()` | Generate random UUID (v4) || `UUID.random` | Most common for primary keys |
166+
| `uuidv4()` | Alias for gen_random_uuid() || `UUID.v4` | Alternative API |
167+
| `uuidv7()` | Generate time-ordered UUID (v7) || `UUID.timeOrdered` | Better index performance |
168+
| `uuidv7(interval)` | Generate shifted time-ordered UUID || `UUID.timeOrdered(shift:)` | For backdating/future-dating |
169+
| `uuid_extract_version()` | Extract UUID version number || `.extractVersion()` | Returns Int? (1-7) |
170+
| `uuid_extract_timestamp()` | Extract timestamp from v1/v7 || `.extractTimestamp()` | Returns Date? (NULL for v4) |
171+
172+
**Coverage**: 6/6 functions (100%)
173+
174+
**Benefits**:
175+
- Server-side UUID generation reduces network roundtrips
176+
- Time-ordered UUIDs (v7) provide better B-tree index performance than random (v4)
177+
- Extract creation time without separate `createdAt` column
178+
- Type-safe APIs with proper NULL handling
179+
180+
**Example Usage**:
181+
```swift
182+
// Generation
183+
Event.insert { Event.Draft(id: .timeOrdered, title: "Login") }
184+
// INSERT INTO "events" ("id", "title") VALUES (uuidv7(), 'Login')
185+
186+
// Extraction
187+
Event.where { $0.id.extractVersion() == 7 }
188+
// SELECT … FROM "events" WHERE uuid_extract_version("events"."id") = 7
189+
190+
// Timestamp extraction
191+
Event.select { $0.id.extractTimestamp() }
192+
// SELECT uuid_extract_timestamp("events"."id") FROM "events"
193+
```
194+
195+
**Implementation Files**:
196+
- `Functions/UUID/UUID+Generation.swift` - Static generation functions
197+
- `Functions/UUID/UUID+Extraction.swift` - Instance extraction methods
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+
}

0 commit comments

Comments
 (0)