Skip to content

Commit 81600ca

Browse files
committed
Add QueryExpression<Optional>.map,flatMap (#80)
* Add `QueryExpression<Optional>.map,flatMap` This PR adds helpers that make it a little easier to work with optional query expressions in a builder. For example, if you want to execute a `LIKE` operator on an optional string, you currently have to resort to one of the following workarounds: ```swift .where { ($0.title ?? "").like("%foo%") } // or: .where { #sql("\($0.title) LIKE '%foo%') } ``` This PR introduces `map` and `flatMap` operations on optional `QueryExpression`s that unwraps the expression, giving you additional flexibility in how you express your builder code: ```swift .where { $0.title.map { $0.like("%foo%") } ?? false } ``` While this is more code than the above options, some may prefer its readability, and should we merge the other optional helpers from #61, it could be further shortened: ```swift .where { $0.title.map { $0.like("%foo%") } } ``` * tests
1 parent 067c919 commit 81600ca

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryExpression.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,8 @@
2828
2929
- ``jsonArrayLength()``
3030
- ``jsonGroupArray(order:filter:)``
31+
32+
### Optionality
33+
34+
- ``map(_:)``
35+
- ``flatMap(_:)``

Sources/StructuredQueriesCore/Optional.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,43 @@ where Wrapped.TableColumns: PrimaryKeyedTableDefinition {
137137
self[dynamicMember: \.primaryKey]
138138
}
139139
}
140+
141+
extension QueryExpression where QueryValue: _OptionalProtocol {
142+
/// Creates and optionalizes a new expression from this one by applying an unwrapped version of
143+
/// this expression to a given closure.
144+
///
145+
/// ```swift
146+
/// Reminder.where {
147+
/// $0.dueDate.map { $0 > Date() }
148+
/// }
149+
/// // SELECT … FROM "reminders"
150+
/// // WHERE "reminders"."dueDate" > '2018-01-29 00:08:00.000'
151+
/// ```
152+
///
153+
/// - Parameter transform: A closure that takes an unwrapped version of this expression.
154+
/// - Returns: The result of the transform function, optionalized.
155+
public func map<T>(
156+
_ transform: (SQLQueryExpression<QueryValue.Wrapped>) -> some QueryExpression<T>
157+
) -> some QueryExpression<T?> {
158+
SQLQueryExpression(transform(SQLQueryExpression(queryFragment)).queryFragment)
159+
}
160+
161+
/// Creates a new optional expression from this one by applying an unwrapped version of this
162+
/// expression to a given closure.
163+
///
164+
/// ```swift
165+
/// Reminder.select {
166+
/// $0.dueDate.flatMap { $0.max() }
167+
/// }
168+
/// // SELECT max("reminders"."dueDate") FROM "reminders"
169+
/// // => [Date?]
170+
/// ```
171+
///
172+
/// - Parameter transform: A closure that takes an unwrapped version of this expression.
173+
/// - Returns: The result of the transform function.
174+
public func flatMap<T>(
175+
_ transform: (SQLQueryExpression<QueryValue.Wrapped>) -> some QueryExpression<T?>
176+
) -> some QueryExpression<T?> {
177+
SQLQueryExpression(transform(SQLQueryExpression(queryFragment)).queryFragment)
178+
}
179+
}

Tests/StructuredQueriesTests/SelectTests.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,50 @@ extension SnapshotTests {
11701170
"""
11711171
}
11721172
}
1173+
1174+
@Test func optionalMapAndFlatMap() {
1175+
do {
1176+
let query: some Statement<Bool?> = Reminder.select {
1177+
$0.priority.map { $0 < Priority.high }
1178+
}
1179+
assertQuery(query) {
1180+
"""
1181+
SELECT ("reminders"."priority" < 3)
1182+
FROM "reminders"
1183+
"""
1184+
} results: {
1185+
"""
1186+
┌───────┐
1187+
│ nil │
1188+
│ nil │
1189+
│ false │
1190+
│ nil │
1191+
│ nil │
1192+
│ false │
1193+
│ true │
1194+
│ false │
1195+
│ nil │
1196+
│ true │
1197+
└───────┘
1198+
"""
1199+
}
1200+
}
1201+
do {
1202+
let query: some Statement<Int?> = Reminder.select { $0.priority.flatMap { $0.max() } }
1203+
assertQuery(query) {
1204+
"""
1205+
SELECT max("reminders"."priority")
1206+
FROM "reminders"
1207+
"""
1208+
} results: {
1209+
"""
1210+
┌───┐
1211+
│ 3 │
1212+
└───┘
1213+
"""
1214+
}
1215+
}
1216+
}
11731217
}
11741218
}
11751219

0 commit comments

Comments
 (0)