import XCTest import GRDB private struct Player: Codable, PersistableRecord, FetchableRecord, Hashable { var id: Int64 var name: String var score: Int var bonus: Int static func createTable(_ db: Database) throws { try db.create(table: "player") { t in t.autoIncrementedPrimaryKey("id") t.column("name", .text) t.column("score", .integer) t.column("bonus", .integer) } } } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension Player: Identifiable { } private enum Columns: String, ColumnExpression { case id, name, score, bonus } private extension QueryInterfaceRequest { func incrementScore(_ db: Database) throws { try updateAll(db, Columns.score += 1) } } class TableRecordUpdateTests: GRDBTestCase { func testRequestUpdateAll() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) let assignment = Columns.score.set(to: 0) try Player.updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try Player.filter(Columns.name == "Arthur").updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE \"name\" = 'Arthur' """) try Player.filter(key: 1).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" = 1 """) try Player.filter(keys: [1, 2]).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (1, 2) """) if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { try Player.filter(id: 1).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" = 1 """) try Player.filter(ids: [1, 2]).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (1, 2) """) } try Player.filter(sql: "id = 1").updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE id = 1 """) try Player.filter(sql: "id = 1").filter(Columns.name == "Arthur").updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE (id = 1) AND (\"name\" = 'Arthur') """) try Player.select(Columns.name).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try Player.order(Columns.name).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) if try String.fetchCursor(db, sql: "PRAGMA COMPILE_OPTIONS").contains("ENABLE_UPDATE_DELETE_LIMIT") { try Player.limit(1).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 LIMIT 1 """) try Player.order(Columns.name).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try Player.order(Columns.name).limit(1).updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 ORDER BY \"name\" LIMIT 1 """) try Player.order(Columns.name).limit(1, offset: 2).reversed().updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 ORDER BY \"name\" DESC LIMIT 1 OFFSET 2 """) try Player.limit(1, offset: 2).reversed().updateAll(db, assignment) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 LIMIT 1 OFFSET 2 """) } } } func testRequestUpdateAndFetchStatement() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER guard sqlite3_libversion_number() >= 3035000 else { throw XCTSkip("RETURNING clause is not available") } #else guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { throw XCTSkip("RETURNING clause is not available") } #endif try makeDatabaseQueue().write { db in try Player.createTable(db) let assignment = Columns.score.set(to: 0) let request = Player.all() let statement = try request.updateAndFetchStatement(db, [assignment], selection: [AllColumns()]) XCTAssertEqual(statement.sql, "UPDATE \"player\" SET \"score\" = ? RETURNING *") XCTAssertEqual(statement.arguments, [0]) XCTAssertEqual(statement.columnNames, ["id", "name", "score", "bonus"]) } } func testRequestUpdateAndFetchCursor() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER guard sqlite3_libversion_number() >= 3035000 else { throw XCTSkip("RETURNING clause is not available") } #else guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { throw XCTSkip("RETURNING clause is not available") } #endif try makeDatabaseQueue().write { db in try Player.createTable(db) try Player(id: 1, name: "Arthur", score: 10, bonus: 0).insert(db) try Player(id: 2, name: "Barbara", score: 20, bonus: 10).insert(db) try Player(id: 3, name: "Craig", score: 30, bonus: 20).insert(db) let request = Player.filter(Columns.id != 2) let cursor = try request.updateAndFetchCursor(db, [Columns.score += 100]) let updatedPlayers = try Array(cursor).sorted(by: { $0.id < $1.id }) XCTAssertEqual(updatedPlayers, [ Player(id: 1, name: "Arthur", score: 110, bonus: 0), Player(id: 3, name: "Craig", score: 130, bonus: 20), ]) } } func testRequestUpdateAndFetchAll() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER guard sqlite3_libversion_number() >= 3035000 else { throw XCTSkip("RETURNING clause is not available") } #else guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { throw XCTSkip("RETURNING clause is not available") } #endif try makeDatabaseQueue().write { db in try Player.createTable(db) try Player(id: 1, name: "Arthur", score: 10, bonus: 0).insert(db) try Player(id: 2, name: "Barbara", score: 20, bonus: 10).insert(db) try Player(id: 3, name: "Craig", score: 30, bonus: 20).insert(db) let request = Player.filter(Columns.id != 2) let updatedPlayers = try request .updateAndFetchAll(db, [Columns.score += 100]) .sorted(by: { $0.id < $1.id }) XCTAssertEqual(updatedPlayers, [ Player(id: 1, name: "Arthur", score: 110, bonus: 0), Player(id: 3, name: "Craig", score: 130, bonus: 20), ]) } } func testRequestUpdateAndFetchSet() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER guard sqlite3_libversion_number() >= 3035000 else { throw XCTSkip("RETURNING clause is not available") } #else guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { throw XCTSkip("RETURNING clause is not available") } #endif try makeDatabaseQueue().write { db in try Player.createTable(db) try Player(id: 1, name: "Arthur", score: 10, bonus: 0).insert(db) try Player(id: 2, name: "Barbara", score: 20, bonus: 10).insert(db) try Player(id: 3, name: "Craig", score: 30, bonus: 20).insert(db) let request = Player.filter(Columns.id != 2) let updatedPlayers = try request.updateAndFetchSet(db, [Columns.score += 100]) XCTAssertEqual(updatedPlayers, [ Player(id: 1, name: "Arthur", score: 110, bonus: 0), Player(id: 3, name: "Craig", score: 130, bonus: 20), ]) } } func testNilAssignment() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score.set(to: nil)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = NULL """) } } func testComplexAssignment() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score.set(to: Columns.score * (Columns.bonus + 1))) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" * ("bonus" + 1) """) } } func testAssignmentSubtractAndAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score -= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" - 1 """) try Player.updateAll(db, Columns.score -= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" - "bonus" """) try Player.updateAll(db, Columns.score -= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" - (-"bonus") """) try Player.updateAll(db, Columns.score -= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" - ("bonus" * 2) """) } } func testAssignmentAddAndAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score += 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" + 1 """) try Player.updateAll(db, Columns.score += Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" + "bonus" """) try Player.updateAll(db, Columns.score += -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" + (-"bonus") """) try Player.updateAll(db, Columns.score += Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" + ("bonus" * 2) """) } } func testAssignmentMultiplyAndAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score *= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" * 1 """) try Player.updateAll(db, Columns.score *= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" * "bonus" """) try Player.updateAll(db, Columns.score *= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" * (-"bonus") """) try Player.updateAll(db, Columns.score *= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" * ("bonus" * 2) """) } } func testAssignmentDivideAndAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score /= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" / 1 """) try Player.updateAll(db, Columns.score /= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" / "bonus" """) try Player.updateAll(db, Columns.score /= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" / (-"bonus") """) try Player.updateAll(db, Columns.score /= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" / ("bonus" * 2) """) } } func testAssignmentBitwiseAndAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score &= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" & 1 """) try Player.updateAll(db, Columns.score &= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" & "bonus" """) try Player.updateAll(db, Columns.score &= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" & (-"bonus") """) try Player.updateAll(db, Columns.score &= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" & ("bonus" * 2) """) } } func testAssignmentBitwiseOrAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score |= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" | 1 """) try Player.updateAll(db, Columns.score |= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" | "bonus" """) try Player.updateAll(db, Columns.score |= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" | (-"bonus") """) try Player.updateAll(db, Columns.score |= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" | ("bonus" * 2) """) } } func testAssignmentLeftShiftAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score <<= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" << 1 """) try Player.updateAll(db, Columns.score <<= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" << "bonus" """) try Player.updateAll(db, Columns.score <<= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" << (-"bonus") """) try Player.updateAll(db, Columns.score <<= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" << ("bonus" * 2) """) } } func testAssignmentRightShiftAssign() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score >>= 1) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" >> 1 """) try Player.updateAll(db, Columns.score >>= Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" >> "bonus" """) try Player.updateAll(db, Columns.score >>= -Columns.bonus) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" >> (-"bonus") """) try Player.updateAll(db, Columns.score >>= Columns.bonus * 2) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = "score" >> ("bonus" * 2) """) } } func testMultipleAssignments() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Columns.score.set(to: 0), Columns.bonus.set(to: 1)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0, "bonus" = 1 """) try Player.updateAll(db, [Columns.score.set(to: 0), Columns.bonus.set(to: 1)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0, "bonus" = 1 """) try Player.all().updateAll(db, Columns.score.set(to: 0), Columns.bonus.set(to: 1)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0, "bonus" = 1 """) try Player.all().updateAll(db, [Columns.score.set(to: 0), Columns.bonus.set(to: 1)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0, "bonus" = 1 """) } } func testUpdateAllWithoutAssignmentDoesNotAccessTheDatabase() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) sqlQueries.removeAll() try XCTAssertEqual(Player.updateAll(db, []), 0) try XCTAssertEqual(Player.all().updateAll(db, []), 0) XCTAssert(sqlQueries.isEmpty) } } func testUpdateAllReturnsNumberOfUpdatedRows() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try db.execute(sql: """ INSERT INTO player (id, name, score, bonus) VALUES (1, 'Arthur', 0, 2); INSERT INTO player (id, name, score, bonus) VALUES (2, 'Barbara', 0, 1); INSERT INTO player (id, name, score, bonus) VALUES (3, 'Craig', 0, 0); INSERT INTO player (id, name, score, bonus) VALUES (4, 'Diane', 0, 3); """) let assignment = Columns.score += 1 try XCTAssertEqual(Player.updateAll(db, assignment), 4) try XCTAssertEqual(Player.filter(key: 1).updateAll(db, assignment), 1) try XCTAssertEqual(Player.filter(key: 5).updateAll(db, assignment), 0) try XCTAssertEqual(Player.filter(Columns.bonus > 1).updateAll(db, assignment), 2) if try String.fetchCursor(db, sql: "PRAGMA COMPILE_OPTIONS").contains("ENABLE_UPDATE_DELETE_LIMIT") { try XCTAssertEqual(Player.limit(1).updateAll(db, assignment), 1) try XCTAssertEqual(Player.limit(2).updateAll(db, assignment), 2) try XCTAssertEqual(Player.limit(2, offset: 3).updateAll(db, assignment), 1) try XCTAssertEqual(Player.limit(10).updateAll(db, assignment), 4) } } } func testQueryInterfaceExtension() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try db.execute(sql: """ INSERT INTO player (id, name, score, bonus) VALUES (1, 'Arthur', 0, 0); INSERT INTO player (id, name, score, bonus) VALUES (2, 'Barbara', 0, 0); INSERT INTO player (id, name, score, bonus) VALUES (3, 'Craig', 0, 0); INSERT INTO player (id, name, score, bonus) VALUES (4, 'Diane', 0, 0); """) try Player.all().incrementScore(db) try XCTAssertEqual(Player.filter(Columns.score == 1).fetchCount(db), 4) try Player.filter(key: 1).incrementScore(db) try XCTAssertEqual(Player.fetchOne(db, key: 1)!.score, 2) } } func testConflictPolicyAbort() throws { struct AbortPlayer: PersistableRecord { static let databaseTableName = "player" static let persistenceConflictPolicy = PersistenceConflictPolicy(insert: .abort, update: .abort) func encode(to container: inout PersistenceContainer) { } } try makeDatabaseQueue().write { db in try Player.createTable(db) try AbortPlayer.updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try AbortPlayer.updateAll(db, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try AbortPlayer.all().updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try AbortPlayer.all().updateAll(db, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) } } func testConflictPolicyIgnore() throws { struct IgnorePlayer: PersistableRecord { static let databaseTableName = "player" static let persistenceConflictPolicy = PersistenceConflictPolicy(insert: .abort, update: .ignore) func encode(to container: inout PersistenceContainer) { } } try makeDatabaseQueue().write { db in try Player.createTable(db) try IgnorePlayer.updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try IgnorePlayer.updateAll(db, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try IgnorePlayer.all().updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try IgnorePlayer.all().updateAll(db, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) } } func testConflictPolicyIgnoreWithTable() throws { struct IgnorePlayer: PersistableRecord { static let databaseTableName = "player" static let persistenceConflictPolicy = PersistenceConflictPolicy(insert: .abort, update: .ignore) func encode(to container: inout PersistenceContainer) { } } let table = Table("player") try makeDatabaseQueue().write { db in try Player.createTable(db) try table.updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try table.updateAll(db, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try table.all().updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try table.all().updateAll(db, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) } } func testConflictPolicyCustom() throws { try makeDatabaseQueue().write { db in try Player.createTable(db) try Player.updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 """) try Player.updateAll(db, onConflict: .ignore, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try Player.updateAll(db, onConflict: .ignore, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try Player.all().updateAll(db, onConflict: .ignore, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) try Player.all().updateAll(db, onConflict: .ignore, [Column("score").set(to: 0)]) XCTAssertEqual(self.lastSQLQuery, """ UPDATE OR IGNORE "player" SET "score" = 0 """) } } func testJoinedRequestUpdate() throws { try makeDatabaseQueue().inDatabase { db in struct Player: MutablePersistableRecord { static let team = belongsTo(Team.self) func encode(to container: inout PersistenceContainer) { preconditionFailure("should not be called") } } struct Team: MutablePersistableRecord { static let players = hasMany(Player.self) func encode(to container: inout PersistenceContainer) { preconditionFailure("should not be called") } } try db.create(table: "team") { t in t.autoIncrementedPrimaryKey("id") t.column("active", .boolean) } try db.create(table: "player") { t in t.autoIncrementedPrimaryKey("id") t.belongsTo("team") t.column("score", .integer) } do { try Player.including(required: Player.team).updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (\ SELECT "player"."id" \ FROM "player" \ JOIN "team" ON "team"."id" = "player"."teamId") """) } do { // Regression test for https://github.com/groue/GRDB.swift/issues/758 try Player.including(required: Player.team.filter(Column("active") == 1)).updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (\ SELECT "player"."id" \ FROM "player" \ JOIN "team" ON ("team"."id" = "player"."teamId") AND ("team"."active" = 1)) """) } do { let alias = TableAlias(name: "p") try Player.aliased(alias).including(required: Player.team).updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (\ SELECT "p"."id" \ FROM "player" "p" \ JOIN "team" ON "team"."id" = "p"."teamId") """) } do { try Team.having(Team.players.isEmpty).updateAll(db, Column("active").set(to: false)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "team" SET "active" = 0 WHERE "id" IN (\ SELECT "team"."id" \ FROM "team" \ LEFT JOIN "player" ON "player"."teamId" = "team"."id" \ GROUP BY "team"."id" \ HAVING COUNT(DISTINCT "player"."id") = 0) """) } do { try Team.including(all: Team.players).updateAll(db, Column("active").set(to: false)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "team" SET "active" = 0 """) } } } func testGroupedRequestUpdate() throws { try makeDatabaseQueue().inDatabase { db in struct Player: MutablePersistableRecord { func encode(to container: inout PersistenceContainer) { preconditionFailure("should not be called") } } struct Passport: MutablePersistableRecord { func encode(to container: inout PersistenceContainer) { preconditionFailure("should not be called") } } try db.create(table: "player") { t in t.autoIncrementedPrimaryKey("id") t.column("score", .integer) } try db.create(table: "passport") { t in t.column("countryCode", .text).notNull() t.column("citizenId", .integer).notNull() t.column("active", .boolean) t.primaryKey(["countryCode", "citizenId"]) } do { try Player.all().groupByPrimaryKey().updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (\ SELECT "id" \ FROM "player" \ GROUP BY "id") """) } do { try Player.all().group(Column.rowID).updateAll(db, Column("score").set(to: 0)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "player" SET "score" = 0 WHERE "id" IN (\ SELECT "id" \ FROM "player" \ GROUP BY "rowid") """) } do { try Passport.all().groupByPrimaryKey().updateAll(db, Column("active").set(to: true)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "passport" SET "active" = 1 WHERE "rowid" IN (\ SELECT "rowid" \ FROM "passport" \ GROUP BY "rowid") """) } do { try Passport.all().group(Column.rowID).updateAll(db, Column("active").set(to: true)) XCTAssertEqual(self.lastSQLQuery, """ UPDATE "passport" SET "active" = 1 WHERE "rowid" IN (\ SELECT "rowid" \ FROM "passport" \ GROUP BY "rowid") """) } } } }