From f1cf5e4a83831fbb85f131b593b130644bd7c3e8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 18 Jul 2025 14:31:37 -0700 Subject: [PATCH] `Where.{and,or,not}()` fixes When called with a predicate-less `Where` it could lead to invalid SQL. --- .../Statements/Where.swift | 8 +- Tests/StructuredQueriesTests/WhereTests.swift | 75 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Sources/StructuredQueriesCore/Statements/Where.swift b/Sources/StructuredQueriesCore/Statements/Where.swift index 2e1704d0..9755e570 100644 --- a/Sources/StructuredQueriesCore/Statements/Where.swift +++ b/Sources/StructuredQueriesCore/Statements/Where.swift @@ -357,6 +357,8 @@ extension Where: SelectStatement { /// - Parameter other: Another where clause. /// - Returns: A where clause that `AND`s the given where clauses together. public func and(_ other: Self) -> Self { + guard !predicates.isEmpty else { return other } + guard !other.predicates.isEmpty else { return self } var `where` = self `where`.predicates = [ """ @@ -373,6 +375,8 @@ extension Where: SelectStatement { /// - Parameter other: Another where clause. /// - Returns: A where clause that `OR`s the given where clauses together. public func or(_ other: Self) -> Self { + guard !predicates.isEmpty else { return other } + guard !other.predicates.isEmpty else { return self } var `where` = self `where`.predicates = [ """ @@ -389,7 +393,9 @@ extension Where: SelectStatement { /// - Returns: A where clause that `NOT`s this where clause. public func not() -> Self { var `where` = self - `where`.predicates = ["NOT (\(`where`.predicates.joined(separator: " AND ")))"] + `where`.predicates = [ + "NOT (\(predicates.isEmpty ? "1" : predicates.joined(separator: " AND ")))" + ] return `where` } diff --git a/Tests/StructuredQueriesTests/WhereTests.swift b/Tests/StructuredQueriesTests/WhereTests.swift index c413d694..b9d8f8d0 100644 --- a/Tests/StructuredQueriesTests/WhereTests.swift +++ b/Tests/StructuredQueriesTests/WhereTests.swift @@ -40,6 +40,36 @@ extension SnapshotTests { └───┘ """ } + assertQuery( + Reminder.all.and(Reminder.where(\.isFlagged)).count() + ) { + """ + SELECT count(*) + FROM "reminders" + WHERE "reminders"."isFlagged" + """ + } results: { + """ + ┌───┐ + │ 2 │ + └───┘ + """ + } + assertQuery( + Reminder.where(\.isFlagged).and(Reminder.all).count() + ) { + """ + SELECT count(*) + FROM "reminders" + WHERE "reminders"."isFlagged" + """ + } results: { + """ + ┌───┐ + │ 2 │ + └───┘ + """ + } } @Test(.snapshots(record: .never)) func emptyResults() { @@ -93,6 +123,36 @@ extension SnapshotTests { └───┘ """ } + assertQuery( + Reminder.all.or(Reminder.where(\.isFlagged)).count() + ) { + """ + SELECT count(*) + FROM "reminders" + WHERE "reminders"."isFlagged" + """ + }results: { + """ + ┌───┐ + │ 2 │ + └───┘ + """ + } + assertQuery( + Reminder.where(\.isFlagged).or(Reminder.all).count() + ) { + """ + SELECT count(*) + FROM "reminders" + WHERE "reminders"."isFlagged" + """ + }results: { + """ + ┌───┐ + │ 2 │ + └───┘ + """ + } } @Test func not() { @@ -128,6 +188,21 @@ extension SnapshotTests { └───┘ """ } + assertQuery( + Reminder.all.not().count() + ) { + """ + SELECT count(*) + FROM "reminders" + WHERE NOT (1) + """ + } results: { + """ + ┌───┐ + │ 0 │ + └───┘ + """ + } } @Test func optionalBoolean() throws {