diff --git a/Sources/StructuredQueriesCore/Operators.swift b/Sources/StructuredQueriesCore/Operators.swift index 0d7b5b3e..a11aafb5 100644 --- a/Sources/StructuredQueriesCore/Operators.swift +++ b/Sources/StructuredQueriesCore/Operators.swift @@ -192,7 +192,7 @@ public func == ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) ? "IS" : "=", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. @@ -202,7 +202,7 @@ public func != ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) ? "IS NOT" : "<>", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS NOT", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. @@ -211,7 +211,7 @@ public func == ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) || isNull(rhs) ? "IS" : "=", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. @@ -220,7 +220,7 @@ public func != ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) || isNull(rhs) ? "IS NOT" : "<>", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS NOT", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. diff --git a/Sources/StructuredQueriesCore/PrimaryKeyed.swift b/Sources/StructuredQueriesCore/PrimaryKeyed.swift index f523c738..1adfb037 100644 --- a/Sources/StructuredQueriesCore/PrimaryKeyed.swift +++ b/Sources/StructuredQueriesCore/PrimaryKeyed.swift @@ -22,6 +22,24 @@ public protocol TableDraft: Table { init(_ primaryTable: PrimaryTable) } +extension TableDraft { + public static subscript( + dynamicMember keyPath: KeyPath> + ) -> some Statement { + SQLQueryExpression("\(PrimaryTable.self[keyPath: keyPath])") + } + + public static subscript( + dynamicMember keyPath: KeyPath> + ) -> SelectOf { + unsafeBitCast(PrimaryTable.self[keyPath: keyPath].asSelect(), to: SelectOf.self) + } + + public static var all: SelectOf { + unsafeBitCast(PrimaryTable.all.asSelect(), to: SelectOf.self) + } +} + /// A type representing a database table's columns. /// /// Don't conform to this protocol directly. Instead, use the `@Table` and `@Column` macros to @@ -38,6 +56,14 @@ where QueryValue: PrimaryKeyedTable { var primaryKey: TableColumn { get } } +extension TableDefinition where QueryValue: TableDraft { + public subscript( + dynamicMember keyPath: KeyPath + ) -> Member { + QueryValue.PrimaryTable.columns[keyPath: keyPath] + } +} + extension PrimaryKeyedTableDefinition { /// A query expression representing the number of rows in this table. /// @@ -60,6 +86,22 @@ extension PrimaryKeyedTable { } } +extension TableDraft { + /// A where clause filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: A `WHERE` clause. + public static func find( + _ primaryKey: PrimaryTable.TableColumns.PrimaryKey.QueryOutput + ) -> Where { + Self.where { _ in + PrimaryTable.columns.primaryKey.eq( + PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } + } +} + extension Where where From: PrimaryKeyedTable { /// Adds a primary key condition to a where clause. /// @@ -70,6 +112,40 @@ extension Where where From: PrimaryKeyedTable { } } +extension Where where From: TableDraft { + /// Adds a primary key condition to a where clause. + /// + /// - Parameter primaryKey: A primary key. + /// - Returns: A where clause with the added primary key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.where { _ in + From.PrimaryTable.columns.primaryKey.eq( + From.PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } + } +} + +extension Select where From: PrimaryKeyedTable { + /// A select statement filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: A select statement filtered by the given key. + public func find(_ primaryKey: From.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.and(From.find(primaryKey)) + } +} + +extension Select where From: TableDraft { + /// A select statement filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: A select statement filtered by the given key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.and(From.find(primaryKey)) + } +} + extension Update where From: PrimaryKeyedTable { /// An update statement filtered by a primary key. /// @@ -80,6 +156,20 @@ extension Update where From: PrimaryKeyedTable { } } +extension Update where From: TableDraft { + /// An update statement filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: An update statement filtered by the given key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.where { _ in + From.PrimaryTable.columns.primaryKey.eq( + From.PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } + } +} + extension Delete where From: PrimaryKeyedTable { /// A delete statement filtered by a primary key. /// @@ -90,12 +180,16 @@ extension Delete where From: PrimaryKeyedTable { } } -extension Select where From: PrimaryKeyedTable { - /// A select statement filtered by a primary key. +extension Delete where From: TableDraft { + /// A delete statement filtered by a primary key. /// /// - Parameter primaryKey: A primary key identifying a table row. - /// - Returns: A select statement filtered by the given key. - public func find(_ primaryKey: From.TableColumns.PrimaryKey.QueryOutput) -> Self { - self.and(From.find(primaryKey)) + /// - Returns: A delete statement filtered by the given key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.where { _ in + From.PrimaryTable.columns.primaryKey.eq( + From.PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } } } diff --git a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift index 6c10b31c..e6b49fd6 100644 --- a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift +++ b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift @@ -187,4 +187,269 @@ self + From.self[keyPath: keyPath] } } + + extension Select where From: TableDraft { + public subscript< + each C: QueryRepresentable, + each J: Table, + S: SelectStatement<(), From.PrimaryTable, ()> + >( + dynamicMember keyPath: KeyPath + ) -> Select<(repeat each C), From, (repeat each J)> + where Columns == (repeat each C), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath].asSelect(), + to: Select<(), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath> + ) -> Select<(repeat each C1, C2), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3), From.PrimaryTable, ()> + > + ) -> Select<(repeat each C1, C2, C3), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4), From.PrimaryTable, ()> + > + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4, C5), From.PrimaryTable, ()> + > + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath> + ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3), From.PrimaryTable, J2> + > + ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, J2>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4), From.PrimaryTable, J2> + > + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, J2>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4, C5), From.PrimaryTable, J2> + > + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, J2>.self + ) + } + + public subscript< + each C: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(), From, (J2, J3)>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select + > + ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, (J2, J3)>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, (J2, J3)>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4, C5), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, (J2, J3)>.self + ) + } + } #endif diff --git a/Sources/StructuredQueriesCore/Statements/Where.swift b/Sources/StructuredQueriesCore/Statements/Where.swift index f4c48bc8..5960d958 100644 --- a/Sources/StructuredQueriesCore/Statements/Where.swift +++ b/Sources/StructuredQueriesCore/Statements/Where.swift @@ -80,6 +80,13 @@ public struct Where { public subscript(dynamicMember keyPath: KeyPath) -> Self { self + From.self[keyPath: keyPath] } + + public subscript( + dynamicMember keyPath: KeyPath> + ) -> Self + where From: TableDraft { + self + unsafeBitCast(From.PrimaryTable.self[keyPath: keyPath], to: Self.self) + } #endif } diff --git a/Sources/StructuredQueriesCore/TableDefinition.swift b/Sources/StructuredQueriesCore/TableDefinition.swift index 38bae167..a979c637 100644 --- a/Sources/StructuredQueriesCore/TableDefinition.swift +++ b/Sources/StructuredQueriesCore/TableDefinition.swift @@ -2,6 +2,7 @@ /// /// Don't conform to this protocol directly. Instead, use the `@Table` and `@Column` macros to /// generate a conformance. +@dynamicMemberLookup public protocol TableDefinition: QueryExpression where QueryValue: Table { /// An array of this table's columns. static var allColumns: [any TableColumnExpression] { get } diff --git a/Tests/StructuredQueriesTests/OperatorsTests.swift b/Tests/StructuredQueriesTests/OperatorsTests.swift index 0e9c6f2b..e21840f0 100644 --- a/Tests/StructuredQueriesTests/OperatorsTests.swift +++ b/Tests/StructuredQueriesTests/OperatorsTests.swift @@ -24,12 +24,12 @@ extension SnapshotTests { } assertInlineSnapshot(of: Row.columns.a == Row.columns.c, as: .sql) { """ - ("rows"."a" = "rows"."c") + ("rows"."a" IS "rows"."c") """ } assertInlineSnapshot(of: Row.columns.a == Row.columns.a, as: .sql) { """ - ("rows"."a" = "rows"."a") + ("rows"."a" IS "rows"."a") """ } assertInlineSnapshot(of: Row.columns.a == nil as Int?, as: .sql) { @@ -64,12 +64,12 @@ extension SnapshotTests { } assertInlineSnapshot(of: Row.columns.a != Row.columns.c, as: .sql) { """ - ("rows"."a" <> "rows"."c") + ("rows"."a" IS NOT "rows"."c") """ } assertInlineSnapshot(of: Row.columns.a != Row.columns.a, as: .sql) { """ - ("rows"."a" <> "rows"."a") + ("rows"."a" IS NOT "rows"."a") """ } assertInlineSnapshot(of: Row.columns.a != nil as Int?, as: .sql) { diff --git a/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift b/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift index 9f693704..6cbacf1d 100644 --- a/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift +++ b/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift @@ -115,6 +115,22 @@ extension SnapshotTests { """ } + assertQuery( + Reminder.Draft.find(1).select { ($0.id, $0.title) } + ) { + """ + SELECT "reminders"."id", "reminders"."title" + FROM "reminders" + WHERE ("reminders"."id" = 1) + """ + } results: { + """ + ┌───┬─────────────┐ + │ 1 │ "Groceries" │ + └───┴─────────────┘ + """ + } + assertQuery( Reminder.select { ($0.id, $0.title) }.find(2) ) { @@ -130,6 +146,22 @@ extension SnapshotTests { └───┴───────────┘ """ } + + assertQuery( + Reminder.Draft.select { ($0.id, $0.title) }.find(2) + ) { + """ + SELECT "reminders"."id", "reminders"."title" + FROM "reminders" + WHERE ("reminders"."id" = 2) + """ + } results: { + """ + ┌───┬───────────┐ + │ 2 │ "Haircut" │ + └───┴───────────┘ + """ + } } @Test func findByIDWithJoin() { diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index 03f1f404..56e80668 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -1126,7 +1126,7 @@ extension SnapshotTests { SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" FROM "remindersLists" LEFT JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") - WHERE ifnull(("reminders"."priority" = 3), 0) + WHERE ifnull(("reminders"."priority" IS 3), 0) """ } results: { """ @@ -1171,6 +1171,119 @@ extension SnapshotTests { } } + @Test func reusableStaticHelperOnDraft() { + assertQuery( + Reminder.Draft.incomplete.select(\.id) + ) { + """ + SELECT "reminders"."id" + FROM "reminders" + WHERE NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ + } + assertQuery( + Reminder.Draft.where { _ in true }.incomplete.select(\.id) + ) { + """ + SELECT "reminders"."id" + FROM "reminders" + WHERE 1 AND NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ + } + assertQuery( + Reminder.Draft.select(\.id).incomplete + ) { + """ + SELECT "reminders"."id" + FROM "reminders" + WHERE NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ + } + assertQuery( + Reminder.Draft.all.incomplete.select(\.id) + ) { + """ + SELECT "reminders"."id" + FROM "reminders" + WHERE NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ + } + } + + @Test func reusableColumnHelperOnDraft() { + assertQuery( + Reminder.Draft.select(\.isHighPriority) + ) { + """ + SELECT ("reminders"."priority" IS 3) + FROM "reminders" + """ + } results: { + """ + ┌───────┐ + │ false │ + │ false │ + │ true │ + │ false │ + │ false │ + │ true │ + │ false │ + │ true │ + │ false │ + │ false │ + └───────┘ + """ + } + } + @Test func optionalMapAndFlatMap() { do { let query: some Statement = Reminder.select { diff --git a/Tests/StructuredQueriesTests/TableTests.swift b/Tests/StructuredQueriesTests/TableTests.swift index 230d61f5..311d9a58 100644 --- a/Tests/StructuredQueriesTests/TableTests.swift +++ b/Tests/StructuredQueriesTests/TableTests.swift @@ -54,6 +54,23 @@ extension SnapshotTests { └─────────────────────────────────────────────┘ """ } + assertQuery(Row.Draft.where { $0.id > Optional(0) }) { + """ + SELECT "rows"."id", "rows"."isDeleted" + FROM "rows" + WHERE NOT ("rows"."isDeleted") AND ("rows"."id" > 0) + ORDER BY "rows"."id" DESC + """ + } results: { + """ + ┌───────────────────────────────────────────────────┐ + │ SnapshotTests.TableTests.DefaultSelect.Row.Draft( │ + │ id: 1, │ + │ isDeleted: false │ + │ ) │ + └───────────────────────────────────────────────────┘ + """ + } assertQuery(Row.select(\.id)) { """ SELECT "rows"."id" @@ -328,6 +345,22 @@ extension SnapshotTests { └────────────────────────────────────────────┘ """ } + assertQuery(Row.Draft.where { $0.id > Optional(0) }) { + """ + SELECT "rows"."id", "rows"."isDeleted" + FROM "rows" + WHERE NOT ("rows"."isDeleted") AND ("rows"."id" > 0) + """ + } results: { + """ + ┌──────────────────────────────────────────────────┐ + │ SnapshotTests.TableTests.DefaultWhere.Row.Draft( │ + │ id: 1, │ + │ isDeleted: false │ + │ ) │ + └──────────────────────────────────────────────────┘ + """ + } assertQuery(Row.unscoped) { """ SELECT "rows"."id", "rows"."isDeleted"