diff --git a/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryExpression.md b/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryExpression.md index f1753714..70b00679 100644 --- a/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryExpression.md +++ b/Sources/StructuredQueriesCore/Documentation.docc/Extensions/QueryExpression.md @@ -28,3 +28,8 @@ - ``jsonArrayLength()`` - ``jsonGroupArray(order:filter:)`` + +### Optionality + +- ``map(_:)`` +- ``flatMap(_:)`` diff --git a/Sources/StructuredQueriesCore/Optional.swift b/Sources/StructuredQueriesCore/Optional.swift index 82c432f0..ccd53d78 100644 --- a/Sources/StructuredQueriesCore/Optional.swift +++ b/Sources/StructuredQueriesCore/Optional.swift @@ -137,3 +137,43 @@ where Wrapped.TableColumns: PrimaryKeyedTableDefinition { self[dynamicMember: \.primaryKey] } } + +extension QueryExpression where QueryValue: _OptionalProtocol { + /// Creates and optionalizes a new expression from this one by applying an unwrapped version of + /// this expression to a given closure. + /// + /// ```swift + /// Reminder.where { + /// $0.dueDate.map { $0 > Date() } + /// } + /// // SELECT … FROM "reminders" + /// // WHERE "reminders"."dueDate" > '2018-01-29 00:08:00.000' + /// ``` + /// + /// - Parameter transform: A closure that takes an unwrapped version of this expression. + /// - Returns: The result of the transform function, optionalized. + public func map( + _ transform: (SQLQueryExpression) -> some QueryExpression + ) -> some QueryExpression { + SQLQueryExpression(transform(SQLQueryExpression(queryFragment)).queryFragment) + } + + /// Creates a new optional expression from this one by applying an unwrapped version of this + /// expression to a given closure. + /// + /// ```swift + /// Reminder.select { + /// $0.dueDate.flatMap { $0.max() } + /// } + /// // SELECT max("reminders"."dueDate") FROM "reminders" + /// // => [Date?] + /// ``` + /// + /// - Parameter transform: A closure that takes an unwrapped version of this expression. + /// - Returns: The result of the transform function. + public func flatMap( + _ transform: (SQLQueryExpression) -> some QueryExpression + ) -> some QueryExpression { + SQLQueryExpression(transform(SQLQueryExpression(queryFragment)).queryFragment) + } +} diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index d1242435..a9579ba9 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -1153,6 +1153,50 @@ extension SnapshotTests { """ } } + + @Test func optionalMapAndFlatMap() { + do { + let query: some Statement = Reminder.select { + $0.priority.map { $0 < Priority.high } + } + assertQuery(query) { + """ + SELECT ("reminders"."priority" < 3) + FROM "reminders" + """ + } results: { + """ + ┌───────┐ + │ nil │ + │ nil │ + │ false │ + │ nil │ + │ nil │ + │ false │ + │ true │ + │ false │ + │ nil │ + │ true │ + └───────┘ + """ + } + } + do { + let query: some Statement = Reminder.select { $0.priority.flatMap { $0.max() } } + assertQuery(query) { + """ + SELECT max("reminders"."priority") + FROM "reminders" + """ + } results: { + """ + ┌───┐ + │ 3 │ + └───┘ + """ + } + } + } } }