Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/Tests/GRDBTests/MutablePersistableRecordChangesTests.swift

625 lines
23 KiB
Swift

import XCTest
import GRDB
private struct Player: FetchableRecord, MutablePersistableRecord, Codable {
static let databaseTableName = "players"
var id: Int64?
var name: String?
var score: Int?
var creationDate: Date?
init(id: Int64? = nil, name: String? = nil, score: Int? = nil, creationDate: Date? = nil) {
self.id = id
self.name = name
self.score = score
self.creationDate = creationDate
}
mutating func willInsert(_ db: Database) throws {
if creationDate == nil {
creationDate = Date()
}
}
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
class MutablePersistableRecordChangesTests: GRDBTestCase {
override func setup(_ dbWriter: some DatabaseWriter) throws {
try dbWriter.write { db in
try db.create(table: "players") { t in
t.primaryKey("id", .integer)
t.column("name", .text)
t.column("score", .integer)
t.column("creationDate", .datetime)
}
}
}
func testDegenerateDatabaseEqualsWithSelf() throws {
struct DegenerateRecord: MutablePersistableRecord {
static let databaseTableName = "degenerated"
func encode(to container: inout PersistenceContainer) {
}
}
let record = DegenerateRecord()
XCTAssertTrue(record.databaseEquals(record))
try XCTAssertTrue(record.databaseChanges(from: record).isEmpty)
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
// table must exist
try db.create(table: DegenerateRecord.databaseTableName) { t in
t.autoIncrementedPrimaryKey("id")
}
let totalChangesCount = db.totalChangesCount
try XCTAssertFalse(record.updateChanges(db, from: record))
XCTAssertEqual(db.totalChangesCount, totalChangesCount)
}
}
func testDatabaseEqualsWithSelf() throws {
do {
let player = Player(id: nil, name: nil, score: nil, creationDate: nil)
XCTAssertTrue(player.databaseEquals(player))
try XCTAssertTrue(player.databaseChanges(from: player).isEmpty)
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertFalse(player.updateChanges(db, from: player))
XCTAssertEqual(db.totalChangesCount, totalChangesCount)
}
}
do {
let player = Player(id: 1, name: "foo", score: 42, creationDate: Date())
XCTAssertTrue(player.databaseEquals(player))
try XCTAssertTrue(player.databaseChanges(from: player).isEmpty)
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertFalse(player.updateChanges(db, from: player))
XCTAssertEqual(db.totalChangesCount, totalChangesCount)
}
}
}
func testRecordValueChange() throws {
let dbQueue = try makeDatabaseQueue()
var player = Player(id: 1, name: "Arthur", score: nil, creationDate: nil)
try dbQueue.inDatabase { db in
try player.insert(db)
}
do {
// non-nil vs. non-nil
var newPlayer = player
newPlayer.name = "Bobby"
XCTAssertFalse(newPlayer.databaseEquals(player))
let changes = try newPlayer.databaseChanges(from: player)
XCTAssertEqual(changes.count, 1)
XCTAssertEqual(changes["name"]!, "Arthur".databaseValue)
try dbQueue.inDatabase { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: player))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
XCTAssertEqual(lastSQLQuery, "UPDATE \"players\" SET \"name\"=\'Bobby\' WHERE \"id\"=1")
}
}
do {
// non-nil vs. nil
var newPlayer = player
newPlayer.name = nil
XCTAssertFalse(newPlayer.databaseEquals(player))
let changes = try newPlayer.databaseChanges(from: player)
XCTAssertEqual(changes.count, 1)
XCTAssertEqual(changes["name"]!, "Arthur".databaseValue)
try dbQueue.inDatabase { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: player))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
XCTAssertEqual(lastSQLQuery, "UPDATE \"players\" SET \"name\"=NULL WHERE \"id\"=1")
}
}
do {
// nil vs. non-nil
var newPlayer = player
newPlayer.score = 41
XCTAssertFalse(newPlayer.databaseEquals(player))
let changes = try newPlayer.databaseChanges(from: player)
XCTAssertEqual(changes.count, 1)
XCTAssertEqual(changes["score"]!, .null)
try dbQueue.inDatabase { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: player))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
XCTAssertEqual(lastSQLQuery, "UPDATE \"players\" SET \"score\"=41 WHERE \"id\"=1")
}
}
do {
// multiple changes
var newPlayer = player
newPlayer.name = "Bobby"
newPlayer.score = 41
XCTAssertFalse(newPlayer.databaseEquals(player))
let changes = try newPlayer.databaseChanges(from: player)
XCTAssertEqual(changes.count, 2)
XCTAssertEqual(changes["name"]!, "Arthur".databaseValue)
XCTAssertEqual(changes["score"]!, .null)
try dbQueue.inDatabase { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: player))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
let fetchedPlayer = try Player.fetchOne(db, key: player.id)
XCTAssertEqual(fetchedPlayer?.name, newPlayer.name)
XCTAssertEqual(fetchedPlayer?.score, newPlayer.score)
}
}
}
func testDatabaseEqualsWithDifferentTypesAndDifferentWidth() throws {
// Mangle column case as well, for fun ;-)
struct NarrowPlayer: MutablePersistableRecord, Codable {
static let databaseTableName = "players"
var ID: Int64?
var NAME: String?
}
let dbQueue = try makeDatabaseQueue()
do {
var oldPlayer = NarrowPlayer(ID: 1, NAME: "Arthur")
let newPlayer = Player(id: 1, name: "Arthur", score: 41, creationDate: nil)
let changes = try newPlayer.databaseChanges(from: oldPlayer)
XCTAssertEqual(changes.count, 1)
XCTAssertEqual(changes["score"]!, .null)
try dbQueue.inTransaction { db in
try oldPlayer.insert(db)
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: oldPlayer))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
XCTAssertEqual(lastSQLQuery, "UPDATE \"players\" SET \"score\"=41 WHERE \"id\"=1")
return .rollback
}
}
do {
var oldPlayer = NarrowPlayer(ID: 1, NAME: "Bobby")
let newPlayer = Player(id: 1, name: "Arthur", score: 42, creationDate: nil)
let changes = try newPlayer.databaseChanges(from: oldPlayer)
XCTAssertEqual(changes.count, 2)
XCTAssertEqual(changes["name"]!, "Bobby".databaseValue)
XCTAssertEqual(changes["score"]!, .null)
try dbQueue.inTransaction { db in
try oldPlayer.insert(db)
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: oldPlayer))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
let fetchedPlayer = try Player.fetchOne(db, key: newPlayer.id)
XCTAssertEqual(fetchedPlayer?.name, newPlayer.name)
XCTAssertEqual(fetchedPlayer?.score, newPlayer.score)
return .rollback
}
}
do {
let oldPlayer = Player(id: 1, name: "Arthur", score: 42, creationDate: nil)
let newPlayer = NarrowPlayer(ID: 1, NAME: "Arthur")
let changes = try newPlayer.databaseChanges(from: oldPlayer)
XCTAssertTrue(changes.isEmpty)
try dbQueue.inTransaction { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertFalse(newPlayer.updateChanges(db, from: oldPlayer))
XCTAssertEqual(db.totalChangesCount, totalChangesCount)
return .rollback
}
}
do {
let oldPlayer = Player(id: 1, name: "Arthur", score: nil, creationDate: nil)
let newPlayer = NarrowPlayer(ID: 1, NAME: "Arthur")
let changes = try newPlayer.databaseChanges(from: oldPlayer)
XCTAssertTrue(changes.isEmpty)
try dbQueue.inTransaction { db in
let totalChangesCount = db.totalChangesCount
try XCTAssertFalse(newPlayer.updateChanges(db, from: oldPlayer))
XCTAssertEqual(db.totalChangesCount, totalChangesCount)
return .rollback
}
}
do {
var oldPlayer = Player(id: 1, name: "Arthur", score: nil, creationDate: nil)
let newPlayer = NarrowPlayer(ID: 1, NAME: "Bobby")
let changes = try newPlayer.databaseChanges(from: oldPlayer)
XCTAssertEqual(changes.count, 1)
XCTAssertEqual(changes["NAME"]!, "Arthur".databaseValue)
try dbQueue.inTransaction { db in
try oldPlayer.insert(db)
let totalChangesCount = db.totalChangesCount
try XCTAssertTrue(newPlayer.updateChanges(db, from: oldPlayer))
XCTAssertEqual(db.totalChangesCount, totalChangesCount + 1)
XCTAssertEqual(lastSQLQuery, "UPDATE \"players\" SET \"name\"='Bobby' WHERE \"id\"=1")
return .rollback
}
}
}
func testUpdateChangesWithRecord() throws {
class MyRecord: Record {
var id: Int64?
var firstName: String?
var lastName: String?
init(id: Int64?, firstName: String?, lastName: String?) {
self.id = id
self.firstName = firstName
self.lastName = lastName
super.init()
}
override class var databaseTableName: String {
"myRecord"
}
enum Columns: String, ColumnExpression {
case id, firstName, lastName
}
required init(row: Row) throws {
id = row[Columns.id]
firstName = row[Columns.firstName]
lastName = row[Columns.lastName]
try super.init(row: row)
}
override func encode(to container: inout PersistenceContainer) throws {
container[Columns.id] = id
container[Columns.firstName] = firstName
container[Columns.lastName] = lastName
}
override func didInsert(_ inserted: InsertionSuccess) {
super.didInsert(inserted)
id = inserted.rowID
}
}
let dbQueue = try makeDatabaseQueue()
try dbQueue.write { db in
try db.create(table: "myRecord") { t in
t.autoIncrementedPrimaryKey("id")
t.column("firstName", .text)
t.column("lastName", .text)
}
var record = MyRecord(id: nil, firstName: "Arthur", lastName: "Smith")
try record.insert(db)
do {
sqlQueries = []
let modified = try record.updateChanges(db) { _ in }
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Arthur"
}
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = nil
}
XCTAssertEqual(record.firstName, nil)
XCTAssertEqual(record.lastName, "Smith")
XCTAssertTrue(modified)
XCTAssertEqual(lastSQLQuery, "UPDATE \"myRecord\" SET \"firstName\"=NULL WHERE \"id\"=1")
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Bob"
$0.lastName = "Johnson"
}
XCTAssertEqual(record.firstName, "Bob")
XCTAssertEqual(record.lastName, "Johnson")
XCTAssertTrue(modified)
XCTAssertTrue([
"UPDATE \"myRecord\" SET \"firstName\"=\'Bob\', \"lastName\"=\'Johnson\' WHERE \"id\"=1",
"UPDATE \"myRecord\" SET \"lastName\"=\'Johnson\', \"firstName\"=\'Bob\' WHERE \"id\"=1"]
.contains(lastSQLQuery))
}
}
}
func testUpdateChangesWithNonRecordClass() throws {
class MyRecord: Codable, PersistableRecord {
var id: Int64?
var firstName: String?
var lastName: String?
init(id: Int64?, firstName: String?, lastName: String?) {
self.id = id
self.firstName = firstName
self.lastName = lastName
}
func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
let dbQueue = try makeDatabaseQueue()
try dbQueue.write { db in
try db.create(table: "myRecord") { t in
t.autoIncrementedPrimaryKey("id")
t.column("firstName", .text)
t.column("lastName", .text)
}
var record = MyRecord(id: nil, firstName: "Arthur", lastName: "Smith")
try record.insert(db)
do {
sqlQueries = []
let modified = try record.updateChanges(db) { _ in }
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Arthur"
}
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = nil
}
XCTAssertEqual(record.firstName, nil)
XCTAssertEqual(record.lastName, "Smith")
XCTAssertTrue(modified)
XCTAssertEqual(lastSQLQuery, "UPDATE \"myRecord\" SET \"firstName\"=NULL WHERE \"id\"=1")
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Bob"
$0.lastName = "Johnson"
}
XCTAssertEqual(record.firstName, "Bob")
XCTAssertEqual(record.lastName, "Johnson")
XCTAssertTrue(modified)
XCTAssertTrue([
"UPDATE \"myRecord\" SET \"firstName\"=\'Bob\', \"lastName\"=\'Johnson\' WHERE \"id\"=1",
"UPDATE \"myRecord\" SET \"lastName\"=\'Johnson\', \"firstName\"=\'Bob\' WHERE \"id\"=1"]
.contains(lastSQLQuery))
}
}
}
func testUpdateChangesWithMutableStruct() throws {
struct MyRecord: Codable, MutablePersistableRecord {
var id: Int64?
var firstName: String?
var lastName: String?
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
let dbQueue = try makeDatabaseQueue()
try dbQueue.write { db in
try db.create(table: "myRecord") { t in
t.autoIncrementedPrimaryKey("id")
t.column("firstName", .text)
t.column("lastName", .text)
}
var record = MyRecord(id: nil, firstName: "Arthur", lastName: "Smith")
try record.insert(db)
do {
sqlQueries = []
let modified = try record.updateChanges(db) { _ in }
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Arthur"
}
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = nil
}
XCTAssertEqual(record.firstName, nil)
XCTAssertEqual(record.lastName, "Smith")
XCTAssertTrue(modified)
XCTAssertEqual(lastSQLQuery, "UPDATE \"myRecord\" SET \"firstName\"=NULL WHERE \"id\"=1")
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Bob"
$0.lastName = "Johnson"
}
XCTAssertEqual(record.firstName, "Bob")
XCTAssertEqual(record.lastName, "Johnson")
XCTAssertTrue(modified)
XCTAssertTrue([
"UPDATE \"myRecord\" SET \"firstName\"=\'Bob\', \"lastName\"=\'Johnson\' WHERE \"id\"=1",
"UPDATE \"myRecord\" SET \"lastName\"=\'Johnson\', \"firstName\"=\'Bob\' WHERE \"id\"=1"]
.contains(lastSQLQuery))
}
}
}
func testUpdateChangesWithImmutableStruct() throws {
struct MyRecord: Codable, PersistableRecord {
var id: Int64
var firstName: String?
var lastName: String?
}
let dbQueue = try makeDatabaseQueue()
try dbQueue.write { db in
try db.create(table: "myRecord") { t in
t.autoIncrementedPrimaryKey("id")
t.column("firstName", .text)
t.column("lastName", .text)
}
var record = MyRecord(id: 1, firstName: "Arthur", lastName: "Smith")
try record.insert(db)
do {
sqlQueries = []
let modified = try record.updateChanges(db) { _ in }
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Arthur"
}
XCTAssertFalse(modified)
XCTAssert(sqlQueries.isEmpty)
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = nil
}
XCTAssertEqual(record.firstName, nil)
XCTAssertEqual(record.lastName, "Smith")
XCTAssertTrue(modified)
XCTAssertEqual(lastSQLQuery, "UPDATE \"myRecord\" SET \"firstName\"=NULL WHERE \"id\"=1")
}
do {
sqlQueries = []
let modified = try record.updateChanges(db) {
$0.firstName = "Bob"
$0.lastName = "Johnson"
}
XCTAssertEqual(record.firstName, "Bob")
XCTAssertEqual(record.lastName, "Johnson")
XCTAssertTrue(modified)
XCTAssertTrue([
"UPDATE \"myRecord\" SET \"firstName\"=\'Bob\', \"lastName\"=\'Johnson\' WHERE \"id\"=1",
"UPDATE \"myRecord\" SET \"lastName\"=\'Johnson\', \"firstName\"=\'Bob\' WHERE \"id\"=1"]
.contains(lastSQLQuery))
}
}
}
func testDatabaseChangesWithClass() throws {
class Player: Encodable, EncodableRecord {
var id: Int64
var name: String
var score: Int
init(id: Int64, name: String, score: Int) {
self.id = id
self.name = name
self.score = score
}
}
var player = Player(id: 1, name: "Arthur", score: 1000)
do {
let changes = try player.databaseChanges { _ in }
XCTAssert(changes.isEmpty)
}
do {
let changes = try player.databaseChanges {
$0.name = "Barbara"
}
XCTAssertEqual(changes, ["name": "Arthur".databaseValue])
}
do {
let changes = try player.databaseChanges {
$0.name = "Craig"
$0.score = 200
}
XCTAssertEqual(changes, ["name": "Barbara".databaseValue, "score": 1000.databaseValue])
}
}
func testDatabaseChangesWithStruct() throws {
struct Player: Encodable, EncodableRecord {
var id: Int64
var name: String
var score: Int
}
var player = Player(id: 1, name: "Arthur", score: 1000)
do {
let changes = try player.databaseChanges { _ in }
XCTAssert(changes.isEmpty)
}
do {
let changes = try player.databaseChanges {
$0.name = "Barbara"
}
XCTAssertEqual(changes, ["name": "Arthur".databaseValue])
}
do {
let changes = try player.databaseChanges {
$0.name = "Craig"
$0.score = 200
}
XCTAssertEqual(changes, ["name": "Barbara".databaseValue, "score": 1000.databaseValue])
}
}
}