2696 lines
118 KiB
Swift
2696 lines
118 KiB
Swift
import XCTest
|
|
import GRDB
|
|
|
|
private struct MutablePersistableRecordPerson : MutablePersistableRecord {
|
|
var id: Int64?
|
|
var name: String?
|
|
var age: Int?
|
|
|
|
static let databaseTableName = "persons"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
// mangle cases
|
|
container["iD"] = id
|
|
container["NAme"] = name
|
|
container["aGe"] = age
|
|
}
|
|
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
let inserted = try insert()
|
|
XCTAssertNotNil(inserted.rowID)
|
|
XCTAssertEqual(inserted.rowIDColumn, "id")
|
|
XCTAssertEqual(inserted.persistenceContainer["iD"]?.databaseValue, inserted.rowID.databaseValue)
|
|
XCTAssertEqual(inserted.persistenceContainer["id"]?.databaseValue, inserted.rowID.databaseValue)
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
XCTAssertEqual(inserted.rowIDColumn, "id")
|
|
XCTAssertEqual(inserted.persistenceContainer["iD"]?.databaseValue, inserted.rowID.databaseValue)
|
|
XCTAssertEqual(inserted.persistenceContainer["id"]?.databaseValue, inserted.rowID.databaseValue)
|
|
id = inserted.rowID
|
|
}
|
|
}
|
|
|
|
private struct MutablePersistableRecordCountry : MutablePersistableRecord {
|
|
var rowID: Int64?
|
|
var isoCode: String
|
|
var name: String
|
|
|
|
static let databaseTableName = "countries"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["isoCode"] = isoCode
|
|
container["name"] = name
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
XCTAssertNil(inserted.rowIDColumn)
|
|
rowID = inserted.rowID
|
|
}
|
|
}
|
|
|
|
private class Callbacks {
|
|
var willInsertCount = 0
|
|
var aroundInsertEnterCount = 0
|
|
var aroundInsertExitCount = 0
|
|
var didInsertCount = 0
|
|
|
|
var willUpdateCount = 0
|
|
var aroundUpdateEnterCount = 0
|
|
var aroundUpdateExitCount = 0
|
|
var didUpdateCount = 0
|
|
|
|
var willSaveCount = 0
|
|
var aroundSaveEnterCount = 0
|
|
var aroundSaveExitCount = 0
|
|
var didSaveCount = 0
|
|
|
|
var willDeleteCount = 0
|
|
var aroundDeleteEnterCount = 0
|
|
var aroundDeleteExitCount = 0
|
|
var didDeleteCount = 0
|
|
}
|
|
|
|
private struct MutablePersistableRecordCustomizedCountry : MutablePersistableRecord {
|
|
var rowID: Int64?
|
|
var isoCode: String
|
|
var name: String
|
|
let callbacks = Callbacks()
|
|
|
|
static let databaseTableName = "countries"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["isoCode"] = isoCode
|
|
container["name"] = name
|
|
}
|
|
|
|
mutating func willInsert(_ db: Database) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
callbacks.willInsertCount += 1
|
|
}
|
|
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
|
|
callbacks.aroundInsertEnterCount += 1
|
|
_ = try insert()
|
|
callbacks.aroundInsertExitCount += 1
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
callbacks.didInsertCount += 1
|
|
rowID = inserted.rowID
|
|
}
|
|
|
|
func willUpdate(_ db: Database, columns: Set<String>) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
callbacks.willUpdateCount += 1
|
|
}
|
|
|
|
func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
|
|
callbacks.aroundUpdateEnterCount += 1
|
|
_ = try update()
|
|
callbacks.aroundUpdateExitCount += 1
|
|
}
|
|
|
|
func didUpdate(_ updated: PersistenceSuccess) {
|
|
callbacks.didUpdateCount += 1
|
|
}
|
|
|
|
func willSave(_ db: Database) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
callbacks.willSaveCount += 1
|
|
}
|
|
|
|
func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
|
|
callbacks.aroundSaveEnterCount += 1
|
|
_ = try save()
|
|
callbacks.aroundSaveExitCount += 1
|
|
}
|
|
|
|
func didSave(_ saved: PersistenceSuccess) {
|
|
callbacks.didSaveCount += 1
|
|
}
|
|
|
|
func willDelete(_ db: Database) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
callbacks.willDeleteCount += 1
|
|
}
|
|
|
|
func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
|
|
// Make sure database can be used
|
|
try db.execute(sql: "SELECT 1")
|
|
|
|
callbacks.aroundDeleteEnterCount += 1
|
|
_ = try delete()
|
|
callbacks.aroundDeleteExitCount += 1
|
|
}
|
|
|
|
func didDelete(deleted: Bool) {
|
|
callbacks.didDeleteCount += 1
|
|
}
|
|
}
|
|
|
|
private struct PartialPlayer: Codable, MutablePersistableRecord, FetchableRecord {
|
|
static let databaseTableName = "player"
|
|
let callbacks = Callbacks()
|
|
var id: Int64?
|
|
var name: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, name
|
|
}
|
|
|
|
mutating func willInsert(_ db: Database) throws {
|
|
callbacks.willInsertCount += 1
|
|
}
|
|
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
callbacks.aroundInsertEnterCount += 1
|
|
_ = try insert()
|
|
callbacks.aroundInsertExitCount += 1
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
id = inserted.rowID
|
|
callbacks.didInsertCount += 1
|
|
}
|
|
|
|
func willUpdate(_ db: Database, columns: Set<String>) throws {
|
|
callbacks.willUpdateCount += 1
|
|
}
|
|
|
|
func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
|
|
callbacks.aroundUpdateEnterCount += 1
|
|
_ = try update()
|
|
callbacks.aroundUpdateExitCount += 1
|
|
}
|
|
|
|
func didUpdate(_ updated: PersistenceSuccess) {
|
|
callbacks.didUpdateCount += 1
|
|
}
|
|
|
|
func willSave(_ db: Database) throws {
|
|
callbacks.willSaveCount += 1
|
|
}
|
|
|
|
func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
|
|
callbacks.aroundSaveEnterCount += 1
|
|
_ = try save()
|
|
callbacks.aroundSaveExitCount += 1
|
|
}
|
|
|
|
func didSave(_ saved: PersistenceSuccess) {
|
|
callbacks.didSaveCount += 1
|
|
}
|
|
|
|
func willDelete(_ db: Database) throws {
|
|
callbacks.willDeleteCount += 1
|
|
}
|
|
|
|
func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
|
|
callbacks.aroundDeleteEnterCount += 1
|
|
_ = try delete()
|
|
callbacks.aroundDeleteExitCount += 1
|
|
}
|
|
|
|
func didDelete(deleted: Bool) {
|
|
callbacks.didDeleteCount += 1
|
|
}
|
|
}
|
|
|
|
private struct FullPlayer: Codable, MutablePersistableRecord, FetchableRecord {
|
|
static let databaseTableName = "player"
|
|
var id: Int64?
|
|
var name: String
|
|
var score: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, name, score
|
|
}
|
|
|
|
let callbacks = Callbacks()
|
|
|
|
mutating func willInsert(_ db: Database) throws {
|
|
callbacks.willInsertCount += 1
|
|
}
|
|
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
callbacks.aroundInsertEnterCount += 1
|
|
_ = try insert()
|
|
callbacks.aroundInsertExitCount += 1
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
id = inserted.rowID
|
|
callbacks.didInsertCount += 1
|
|
}
|
|
|
|
func willUpdate(_ db: Database, columns: Set<String>) throws {
|
|
callbacks.willUpdateCount += 1
|
|
}
|
|
|
|
func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
|
|
callbacks.aroundUpdateEnterCount += 1
|
|
_ = try update()
|
|
callbacks.aroundUpdateExitCount += 1
|
|
}
|
|
|
|
func didUpdate(_ updated: PersistenceSuccess) {
|
|
callbacks.didUpdateCount += 1
|
|
}
|
|
|
|
func willSave(_ db: Database) throws {
|
|
callbacks.willSaveCount += 1
|
|
}
|
|
|
|
func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
|
|
callbacks.aroundSaveEnterCount += 1
|
|
_ = try save()
|
|
callbacks.aroundSaveExitCount += 1
|
|
}
|
|
|
|
func didSave(_ saved: PersistenceSuccess) {
|
|
callbacks.didSaveCount += 1
|
|
}
|
|
|
|
func willDelete(_ db: Database) throws {
|
|
callbacks.willDeleteCount += 1
|
|
}
|
|
|
|
func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
|
|
callbacks.aroundDeleteEnterCount += 1
|
|
_ = try delete()
|
|
callbacks.aroundDeleteExitCount += 1
|
|
}
|
|
|
|
func didDelete(deleted: Bool) {
|
|
callbacks.didDeleteCount += 1
|
|
}
|
|
}
|
|
|
|
class MutablePersistableRecordTests: GRDBTestCase {
|
|
|
|
override func setup(_ dbWriter: some DatabaseWriter) throws {
|
|
var migrator = DatabaseMigrator()
|
|
migrator.registerMigration("setUp") { db in
|
|
try db.execute(sql: """
|
|
CREATE TABLE persons (
|
|
id INTEGER PRIMARY KEY,
|
|
name NOT NULL,
|
|
age INTEGER);
|
|
CREATE TABLE countries (
|
|
isoCode TEXT NOT NULL PRIMARY KEY,
|
|
name TEXT NOT NULL);
|
|
CREATE TABLE player(
|
|
id INTEGER PRIMARY KEY,
|
|
name NOT NULL UNIQUE, -- UNIQUE for upsert tests
|
|
score INTEGER NOT NULL DEFAULT 1000);
|
|
""")
|
|
}
|
|
try migrator.migrate(dbWriter)
|
|
}
|
|
|
|
// MARK: - MutablePersistableRecordPerson
|
|
|
|
func testInsertMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
try person.insert(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
}
|
|
}
|
|
|
|
func testInsertedMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person = try MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24).inserted(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
}
|
|
}
|
|
|
|
func testUpdateWithoutExplicitPrimaryKeyButWithExplicitRowIDSupport() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "CREATE TABLE record(name)")
|
|
|
|
struct Record: Codable, MutablePersistableRecord {
|
|
var rowID: Int64?
|
|
var name: String
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
rowID = inserted.rowID
|
|
}
|
|
}
|
|
|
|
var record1 = Record(name: "Arthur")
|
|
var record2 = Record(name: "Barbara")
|
|
try record1.insert(db)
|
|
try record2.insert(db)
|
|
record1.name = "Craig"
|
|
try record1.update(db)
|
|
|
|
let names = try String.fetchAll(db, sql: "SELECT name FROM record ORDER BY rowid")
|
|
XCTAssertEqual(names, ["Craig", "Barbara"])
|
|
}
|
|
}
|
|
|
|
func testUpdateWithoutExplicitPrimaryKeyAndWithoutExplicitRowIDSupport() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "CREATE TABLE record(name)")
|
|
|
|
struct Record: Codable, PersistableRecord {
|
|
var name: String
|
|
}
|
|
|
|
let record = Record(name: "Arthur")
|
|
try record.insert(db)
|
|
do {
|
|
try record.update(db)
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound { }
|
|
}
|
|
}
|
|
|
|
func testUpdateMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person1 = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
try person1.insert(db)
|
|
var person2 = MutablePersistableRecordPerson(id: nil, name: "Barbara", age: 24)
|
|
try person2.insert(db)
|
|
|
|
person1.name = "Craig"
|
|
try person1.update(db)
|
|
XCTAssertTrue([
|
|
"UPDATE \"persons\" SET \"age\"=24, \"name\"='Craig' WHERE \"id\"=1",
|
|
"UPDATE \"persons\" SET \"name\"='Craig', \"age\"=24 WHERE \"id\"=1"
|
|
].contains(self.lastSQLQuery))
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
}
|
|
}
|
|
|
|
func testPartialUpdateMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person1 = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
try person1.insert(db)
|
|
var person2 = MutablePersistableRecordPerson(id: nil, name: "Barbara", age: 36)
|
|
try person2.insert(db)
|
|
|
|
do {
|
|
person1.name = "Craig"
|
|
try person1.update(db, columns: [String]())
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"id\"=1 WHERE \"id\"=1")
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
XCTAssertEqual(rows[0]["age"] as Int, 24)
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
XCTAssertEqual(rows[1]["age"] as Int, 36)
|
|
}
|
|
|
|
do {
|
|
person1.name = "Craig"
|
|
person1.age = 25
|
|
try person1.update(db, columns: [Column("name")])
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"name\"='Craig' WHERE \"id\"=1")
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[0]["age"] as Int, 24)
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
XCTAssertEqual(rows[1]["age"] as Int, 36)
|
|
}
|
|
|
|
do {
|
|
person1.name = "David"
|
|
try person1.update(db, columns: ["AgE"]) // case insensitivity
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"persons\" SET \"age\"=25 WHERE \"id\"=1")
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[0]["age"] as Int, 25)
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
XCTAssertEqual(rows[1]["age"] as Int, 36)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testSaveMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person1 = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
try person1.save(db)
|
|
|
|
var rows = try Row.fetchAll(db, sql: "SELECT * FROM persons")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
|
|
var person2 = MutablePersistableRecordPerson(id: nil, name: "Barbara", age: 24)
|
|
try person2.save(db)
|
|
|
|
person1.name = "Craig"
|
|
try person1.save(db)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
|
|
try person1.delete(db)
|
|
try person1.save(db)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
}
|
|
}
|
|
|
|
func testSavedMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person1 = try MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24).saved(db)
|
|
|
|
var rows = try Row.fetchAll(db, sql: "SELECT * FROM persons")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
|
|
let person2 = try MutablePersistableRecordPerson(id: nil, name: "Barbara", age: 24).saved(db)
|
|
|
|
person1.name = "Craig"
|
|
var savedPerson1 = try person1.saved(db)
|
|
XCTAssertEqual(person1.id, savedPerson1.id)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
|
|
try person1.delete(db)
|
|
savedPerson1 = try person1.saved(db)
|
|
XCTAssertEqual(person1.id, savedPerson1.id)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, savedPerson1.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Craig")
|
|
XCTAssertEqual(rows[1]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "Barbara")
|
|
}
|
|
}
|
|
|
|
func testDeleteWithoutExplicitPrimaryKeyButWithExplicitRowIDSupport() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "CREATE TABLE record(name)")
|
|
|
|
struct Record: Codable, MutablePersistableRecord {
|
|
var rowID: Int64?
|
|
var name: String
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
rowID = inserted.rowID
|
|
}
|
|
}
|
|
|
|
var record1 = Record(name: "Arthur")
|
|
var record2 = Record(name: "Barbara")
|
|
try record1.insert(db)
|
|
try record2.insert(db)
|
|
try record1.delete(db)
|
|
|
|
let names = try String.fetchAll(db, sql: "SELECT name FROM record ORDER BY rowid")
|
|
XCTAssertEqual(names, ["Barbara"])
|
|
}
|
|
}
|
|
|
|
func testDeleteWithoutExplicitPrimaryKeyAndWithoutExplicitRowIDSupport() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "CREATE TABLE record(name)")
|
|
|
|
struct Record: Codable, PersistableRecord {
|
|
var name: String
|
|
}
|
|
|
|
let record = Record(name: "Arthur")
|
|
try record.insert(db)
|
|
try XCTAssertFalse(record.delete(db))
|
|
|
|
let names = try String.fetchAll(db, sql: "SELECT name FROM record ORDER BY rowid")
|
|
XCTAssertEqual(names, ["Arthur"])
|
|
}
|
|
}
|
|
|
|
func testDeleteMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person1 = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
try person1.insert(db)
|
|
var person2 = MutablePersistableRecordPerson(id: nil, name: "Barbara", age: 24)
|
|
try person2.insert(db)
|
|
|
|
var deleted = try person1.delete(db)
|
|
XCTAssertTrue(deleted)
|
|
deleted = try person1.delete(db)
|
|
XCTAssertFalse(deleted)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons ORDER BY id")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["id"] as Int64, person2.id!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Barbara")
|
|
}
|
|
}
|
|
|
|
func testExistsMutablePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var person = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
try person.insert(db)
|
|
XCTAssertTrue(try person.exists(db))
|
|
|
|
try person.delete(db)
|
|
XCTAssertFalse(try person.exists(db))
|
|
}
|
|
}
|
|
|
|
func testMutablePersistableRecordPersonDatabaseDictionary() throws {
|
|
let person = MutablePersistableRecordPerson(id: nil, name: "Arthur", age: 24)
|
|
let dict = try person.databaseDictionary
|
|
XCTAssertEqual(dict, ["iD": DatabaseValue.null, "NAme": "Arthur".databaseValue, "aGe": 24.databaseValue])
|
|
}
|
|
|
|
// MARK: - MutablePersistableRecordCountry
|
|
|
|
func testInsertMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country = MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France")
|
|
try country.insert(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
}
|
|
}
|
|
|
|
func testInsertedMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country = try MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France").inserted(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
}
|
|
}
|
|
|
|
func testUpdateMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France")
|
|
try country1.insert(db)
|
|
var country2 = MutablePersistableRecordCountry(rowID: nil, isoCode: "US", name: "United States")
|
|
try country2.insert(db)
|
|
|
|
country1.name = "France Métropolitaine"
|
|
try country1.update(db)
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"name\"='France Métropolitaine' WHERE \"isoCode\"='FR'")
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testSaveMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France")
|
|
try country1.save(db)
|
|
|
|
var rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
|
|
var country2 = MutablePersistableRecordCountry(rowID: nil, isoCode: "US", name: "United States")
|
|
try country2.save(db)
|
|
|
|
country1.name = "France Métropolitaine"
|
|
try country1.save(db)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
|
|
try country1.delete(db)
|
|
try country1.save(db)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "France Métropolitaine")
|
|
}
|
|
}
|
|
|
|
func testSavedMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = try MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France").saved(db)
|
|
|
|
var rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
|
|
let country2 = try MutablePersistableRecordCountry(rowID: nil, isoCode: "US", name: "United States").saved(db)
|
|
|
|
country1.name = "France Métropolitaine"
|
|
var savedCountry1 = try country1.saved(db)
|
|
XCTAssertEqual(country1.rowID, savedCountry1.rowID)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
|
|
try country1.delete(db)
|
|
savedCountry1 = try country1.saved(db)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, savedCountry1.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "France Métropolitaine")
|
|
}
|
|
}
|
|
|
|
func testDeleteMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France")
|
|
try country1.insert(db)
|
|
var country2 = MutablePersistableRecordCountry(rowID: nil, isoCode: "US", name: "United States")
|
|
try country2.insert(db)
|
|
|
|
var deleted = try country1.delete(db)
|
|
XCTAssertTrue(deleted)
|
|
deleted = try country1.delete(db)
|
|
XCTAssertFalse(deleted)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testExistsMutablePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country = MutablePersistableRecordCountry(rowID: nil, isoCode: "FR", name: "France")
|
|
try country.insert(db)
|
|
XCTAssertTrue(try country.exists(db))
|
|
|
|
try country.delete(db)
|
|
XCTAssertFalse(try country.exists(db))
|
|
}
|
|
}
|
|
|
|
// MARK: - MutablePersistableRecordCustomizedCountry
|
|
|
|
func testInsertMutablePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country.insert(db)
|
|
|
|
XCTAssertEqual(country.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(country.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(country.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didDeleteCount, 0)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
}
|
|
}
|
|
|
|
func testUpdateMutablePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country1.insert(db)
|
|
var country2 = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "US",
|
|
name: "United States")
|
|
try country2.insert(db)
|
|
|
|
country1.name = "France Métropolitaine"
|
|
try country1.update(db)
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"name\"='France Métropolitaine' WHERE \"isoCode\"='FR'")
|
|
|
|
XCTAssertEqual(country1.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willSaveCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveEnterCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(country1.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(country1.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(country1.callbacks.didDeleteCount, 0)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testRecordErrorMutablePersistableRecordCustomizedCountry() throws {
|
|
let country = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "FR",
|
|
name: "France")
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
do {
|
|
try dbQueue.inDatabase { db in
|
|
try country.update(db)
|
|
}
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "countries", key: ["isoCode": "FR".databaseValue]) { }
|
|
|
|
XCTAssertEqual(country.callbacks.willInsertCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundInsertEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundInsertExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didInsertCount, 0)
|
|
|
|
XCTAssertEqual(country.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateExitCount, 0) // last update has failed
|
|
XCTAssertEqual(country.callbacks.didUpdateCount, 0) // last update has failed
|
|
|
|
XCTAssertEqual(country.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveExitCount, 0) // last update has failed
|
|
XCTAssertEqual(country.callbacks.didSaveCount, 0) // last update has failed
|
|
|
|
XCTAssertEqual(country.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didDeleteCount, 0)
|
|
}
|
|
|
|
func testSaveMutablePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country1.save(db)
|
|
|
|
XCTAssertEqual(country1.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateExitCount, 0) // last update has failed
|
|
XCTAssertEqual(country1.callbacks.didUpdateCount, 0) // last update has failed
|
|
|
|
XCTAssertEqual(country1.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(country1.callbacks.didDeleteCount, 0)
|
|
|
|
var rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
|
|
var country2 = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "US",
|
|
name: "United States")
|
|
try country2.save(db)
|
|
|
|
country1.name = "France Métropolitaine"
|
|
try country1.save(db)
|
|
|
|
XCTAssertEqual(country1.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willSaveCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveEnterCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(country1.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(country1.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(country1.callbacks.didDeleteCount, 0)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
|
|
_ = try country1.delete(db)
|
|
try country1.save(db)
|
|
|
|
XCTAssertEqual(country1.callbacks.willInsertCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertEnterCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertExitCount, 2)
|
|
XCTAssertEqual(country1.callbacks.didInsertCount, 2)
|
|
|
|
XCTAssertEqual(country1.callbacks.willUpdateCount, 3)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateEnterCount, 3)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateExitCount, 1) // last update has failed
|
|
XCTAssertEqual(country1.callbacks.didUpdateCount, 1) // last update has failed
|
|
|
|
XCTAssertEqual(country1.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveExitCount, 3)
|
|
XCTAssertEqual(country1.callbacks.didSaveCount, 3)
|
|
|
|
XCTAssertEqual(country1.callbacks.willDeleteCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didDeleteCount, 1)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
XCTAssertEqual(rows[1]["rowID"] as Int64, country1.rowID!)
|
|
XCTAssertEqual(rows[1]["name"] as String, "France Métropolitaine")
|
|
}
|
|
}
|
|
|
|
func testDeleteMutablePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country1.insert(db)
|
|
var country2 = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "US",
|
|
name: "United States")
|
|
try country2.insert(db)
|
|
|
|
var deleted = try country1.delete(db)
|
|
XCTAssertTrue(deleted)
|
|
deleted = try country1.delete(db)
|
|
XCTAssertFalse(deleted)
|
|
|
|
XCTAssertEqual(country1.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(country1.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(country1.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(country1.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(country1.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(country1.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(country1.callbacks.willDeleteCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteEnterCount, 2)
|
|
XCTAssertEqual(country1.callbacks.aroundDeleteExitCount, 2)
|
|
XCTAssertEqual(country1.callbacks.didDeleteCount, 2)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT rowID, * FROM countries ORDER BY rowID")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["rowID"] as Int64, country2.rowID!)
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testExistsMutablePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country = MutablePersistableRecordCustomizedCountry(
|
|
rowID: nil,
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country.insert(db)
|
|
|
|
XCTAssertTrue(try country.exists(db))
|
|
|
|
XCTAssertEqual(country.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(country.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(country.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didDeleteCount, 0)
|
|
|
|
_ = try country.delete(db)
|
|
|
|
XCTAssertFalse(try country.exists(db))
|
|
|
|
XCTAssertEqual(country.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(country.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(country.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(country.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(country.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(country.callbacks.willDeleteCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteEnterCount, 1)
|
|
XCTAssertEqual(country.callbacks.aroundDeleteExitCount, 1)
|
|
XCTAssertEqual(country.callbacks.didDeleteCount, 1)
|
|
}
|
|
}
|
|
|
|
// MARK: - Misc
|
|
|
|
func testPartiallyEncodedRecord() throws {
|
|
struct PartialRecord : MutablePersistableRecord {
|
|
var id: Int64?
|
|
var a: String
|
|
|
|
static let databaseTableName = "records"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["id"] = id
|
|
container["a"] = a
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
id = inserted.rowID
|
|
}
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "records") { t in
|
|
t.primaryKey("id", .integer)
|
|
t.column("a", .text)
|
|
t.column("b", .text)
|
|
t.column("c", .integer).notNull().defaults(to: 123)
|
|
}
|
|
|
|
// Insertion only inserts defined columns
|
|
var record = PartialRecord(id: nil, a: "foo")
|
|
try record.insert(db)
|
|
XCTAssertTrue(
|
|
["INSERT INTO \"records\" (\"id\", \"a\") VALUES (NULL,'foo')",
|
|
"INSERT INTO \"records\" (\"a\", \"id\") VALUES ('foo',NULL)"]
|
|
.contains(lastSQLQuery))
|
|
XCTAssertEqual(try Row.fetchOne(db, sql: "SELECT * FROM records")!, ["id": 1, "a": "foo", "b": nil, "c": 123])
|
|
|
|
// Update only updates defined columns
|
|
record.a = "bar"
|
|
try record.update(db)
|
|
XCTAssertEqual(lastSQLQuery, "UPDATE \"records\" SET \"a\"='bar' WHERE \"id\"=1")
|
|
XCTAssertEqual(try Row.fetchOne(db, sql: "SELECT * FROM records")!, ["id": 1, "a": "bar", "b": nil, "c": 123])
|
|
|
|
// Update always update something
|
|
record.a = "baz"
|
|
try record.update(db, columns: ["b"])
|
|
XCTAssertEqual(lastSQLQuery, "UPDATE \"records\" SET \"id\"=1 WHERE \"id\"=1")
|
|
XCTAssertEqual(try Row.fetchOne(db, sql: "SELECT * FROM records")!, ["id": 1, "a": "bar", "b": nil, "c": 123])
|
|
|
|
// Deletion
|
|
try record.delete(db)
|
|
XCTAssertEqual(lastSQLQuery, "DELETE FROM \"records\" WHERE \"id\"=1")
|
|
XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM records")!, 0)
|
|
|
|
// Expect database errors when missing columns must have a value
|
|
try db.drop(table: "records")
|
|
try db.create(table: "records") { t in
|
|
t.primaryKey("id", .integer)
|
|
t.column("a", .text)
|
|
t.column("b", .text).notNull()
|
|
}
|
|
do {
|
|
try record.insert(db)
|
|
} catch let error as DatabaseError {
|
|
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
|
|
// actual error message depends on the SQLite version
|
|
XCTAssertTrue(
|
|
["NOT NULL constraint failed: records.b",
|
|
"records.b may not be NULL"].contains(error.message!))
|
|
}
|
|
}
|
|
}
|
|
|
|
func testRecordErrorRecordNotFoundDescription() {
|
|
do {
|
|
let error = RecordError.recordNotFound(
|
|
databaseTableName: "place",
|
|
key: ["id": .null])
|
|
XCTAssertEqual(
|
|
error.description,
|
|
"Key not found in table place: [id:NULL]")
|
|
}
|
|
do {
|
|
let error = RecordError.recordNotFound(
|
|
databaseTableName: "user",
|
|
key: ["uuid": "E621E1F8-C36C-495A-93FC-0C247A3E6E5F".databaseValue])
|
|
XCTAssertEqual(
|
|
error.description,
|
|
"Key not found in table user: [uuid:\"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\"]")
|
|
}
|
|
}
|
|
|
|
func testGeneratedColumnsInsertIsAnError() throws {
|
|
#if !GRDBCUSTOMSQLITE
|
|
throw XCTSkip("Generated columns are not available")
|
|
#else
|
|
struct T: MutablePersistableRecord {
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["a"] = 1
|
|
container["b"] = 1
|
|
}
|
|
}
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "CREATE TABLE t (a, b ALWAYS GENERATED AS (a))")
|
|
do {
|
|
var record = T()
|
|
try record.insert(db)
|
|
XCTFail("Expected error")
|
|
} catch let error as DatabaseError {
|
|
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
|
|
XCTAssertEqual(error.message!, "cannot INSERT into generated column \"b\"")
|
|
XCTAssertEqual(error.sql!, "INSERT INTO \"t\" (\"a\", \"b\") VALUES (?,?)")
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func testGeneratedColumnsUpdateIsAnError() throws {
|
|
#if !GRDBCUSTOMSQLITE
|
|
throw XCTSkip("Generated columns are not available")
|
|
#else
|
|
struct T: MutablePersistableRecord {
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["id"] = 1
|
|
container["a"] = 1
|
|
}
|
|
}
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "CREATE TABLE t (id INTEGER PRIMARY KEY, a ALWAYS GENERATED AS (id))")
|
|
do {
|
|
try T().update(db)
|
|
XCTFail("Expected error")
|
|
} catch let error as DatabaseError {
|
|
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
|
|
XCTAssertEqual(error.message!, "cannot UPDATE generated column \"a\"")
|
|
XCTAssertEqual(error.sql!, "UPDATE \"t\" SET \"a\"=? WHERE \"id\"=?")
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// MARK: - Insert and Fetch
|
|
|
|
extension MutablePersistableRecordTests {
|
|
func test_insertAndFetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let player = FullPlayer(id: nil, name: "Arthur", score: 1000)
|
|
let insertedPlayer = try XCTUnwrap(player.insertAndFetch(db))
|
|
XCTAssertEqual(insertedPlayer.id, 1)
|
|
XCTAssertEqual(insertedPlayer.name, "Arthur")
|
|
XCTAssertEqual(insertedPlayer.score, 1000)
|
|
}
|
|
}
|
|
|
|
func test_insertAndFetch_as() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(name: "Arthur")
|
|
let fullPlayer = try XCTUnwrap(partialPlayer.insertAndFetch(db, as: FullPlayer.self))
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") VALUES (NULL,'Arthur') RETURNING *
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.name, "Arthur")
|
|
XCTAssertEqual(fullPlayer.score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func test_insertAndFetch_selection_fetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(name: "Arthur")
|
|
let score = try partialPlayer.insertAndFetch(db, selection: [Column("score")]) { (statement: Statement) in
|
|
try Int.fetchOne(statement)!
|
|
}
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") VALUES (NULL,'Arthur') RETURNING "score"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
|
|
do {
|
|
// Test onConflict: .ignore
|
|
sqlQueries.removeAll()
|
|
var player = FullPlayer(id: 1, name: "Barbara", score: 100)
|
|
try XCTAssertTrue(player.exists(db))
|
|
let score = try player.insertAndFetch(db, onConflict: .ignore, selection: [Column("score")]) { (statement: Statement) in
|
|
try Int.fetchOne(statement)
|
|
}
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT OR IGNORE INTO "player" ("id", "name", "score") VALUES (1,'Barbara',100) RETURNING "score"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(player.id, 1)
|
|
XCTAssertNil(score)
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Save and Fetch
|
|
|
|
extension MutablePersistableRecordTests {
|
|
func test_saveAndFetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let player = FullPlayer(id: nil, name: "Arthur", score: 1000)
|
|
let savedPlayer = try XCTUnwrap(player.saveAndFetch(db))
|
|
XCTAssertEqual(savedPlayer.id, 1)
|
|
XCTAssertEqual(savedPlayer.name, "Arthur")
|
|
XCTAssertEqual(savedPlayer.score, 1000)
|
|
}
|
|
}
|
|
|
|
func test_saveAndFetch_as() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(name: "Arthur")
|
|
let fullPlayer = try XCTUnwrap(partialPlayer.saveAndFetch(db, as: FullPlayer.self))
|
|
|
|
XCTAssert(sqlQueries.allSatisfy { !$0.contains("UPDATE") })
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") VALUES (NULL,'Arthur') RETURNING *
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.name, "Arthur")
|
|
XCTAssertEqual(fullPlayer.score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
|
|
do {
|
|
var partialPlayer = PartialPlayer(id: 1, name: "Arthur")
|
|
try partialPlayer.delete(db)
|
|
sqlQueries.removeAll()
|
|
let fullPlayer = try XCTUnwrap(partialPlayer.saveAndFetch(db, as: FullPlayer.self))
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
UPDATE "player" SET "name"='Arthur' WHERE "id"=1 RETURNING *
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") VALUES (1,'Arthur') RETURNING *
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.name, "Arthur")
|
|
XCTAssertEqual(fullPlayer.score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 1)
|
|
}
|
|
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(id: 1, name: "Arthur")
|
|
let fullPlayer = try XCTUnwrap(partialPlayer.saveAndFetch(db, as: FullPlayer.self))
|
|
|
|
XCTAssert(sqlQueries.allSatisfy { !$0.contains("INSERT") })
|
|
XCTAssert(sqlQueries.contains("""
|
|
UPDATE "player" SET "name"='Arthur' WHERE "id"=1 RETURNING *
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.name, "Arthur")
|
|
XCTAssertEqual(fullPlayer.score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func test_saveAndFetch_selection_fetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(name: "Arthur")
|
|
let score = try partialPlayer.saveAndFetch(db, selection: [Column("score")]) { (statement: Statement) in
|
|
try Int.fetchOne(statement)
|
|
}
|
|
|
|
XCTAssert(sqlQueries.allSatisfy { !$0.contains("UPDATE") })
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") VALUES (NULL,'Arthur') RETURNING "score"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
|
|
do {
|
|
var partialPlayer = PartialPlayer(id: 1, name: "Arthur")
|
|
try partialPlayer.delete(db)
|
|
sqlQueries.removeAll()
|
|
let score = try partialPlayer.saveAndFetch(db, selection: [Column("score")]) { (statement: Statement) in
|
|
try Int.fetchOne(statement)
|
|
}
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
UPDATE "player" SET "name"='Arthur' WHERE "id"=1 RETURNING "score"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") VALUES (1,'Arthur') RETURNING "score"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 1)
|
|
}
|
|
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(id: 1, name: "Arthur")
|
|
let score = try partialPlayer.saveAndFetch(db, selection: [Column("score")]) { (statement: Statement) in
|
|
try Int.fetchOne(statement)
|
|
}
|
|
|
|
XCTAssert(sqlQueries.allSatisfy { !$0.contains("INSERT") })
|
|
XCTAssert(sqlQueries.contains("""
|
|
UPDATE "player" SET "name"='Arthur' WHERE "id"=1 RETURNING "score"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Update and Fetch
|
|
|
|
extension MutablePersistableRecordTests {
|
|
func test_updateAndFetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateAndFetch(db)
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
player.name = "Barbara"
|
|
|
|
do {
|
|
let updatedPlayer = try XCTUnwrap(player.updateAndFetch(db))
|
|
XCTAssertEqual(updatedPlayer.id, 1)
|
|
XCTAssertEqual(updatedPlayer.name, "Barbara")
|
|
XCTAssertEqual(updatedPlayer.score, 1000)
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
|
|
func test_updateAndFetch_as() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateAndFetch(db, as: PartialPlayer.self)
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
player.name = "Barbara"
|
|
|
|
do {
|
|
let updatedPlayer = try XCTUnwrap(player.updateAndFetch(db, as: PartialPlayer.self))
|
|
XCTAssertEqual(updatedPlayer.id, 1)
|
|
XCTAssertEqual(updatedPlayer.name, "Barbara")
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
|
|
func test_updateAndFetch_selection_fetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateAndFetch(db, selection: [AllColumns()]) { statement in
|
|
try Row.fetchOne(statement)
|
|
}
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
player.name = "Barbara"
|
|
player.score = 0
|
|
|
|
do {
|
|
let row = try player.updateAndFetch(db, selection: [AllColumns()]) { statement in
|
|
try Row.fetchOne(statement)
|
|
}
|
|
XCTAssertEqual(row, ["id": 1, "name": "Barbara", "score": 0])
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
|
|
func test_updateAndFetch_columns_selection_fetch() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateAndFetch(db, columns: [Column("score")], selection: [AllColumns()]) { statement in
|
|
try Row.fetchOne(statement)
|
|
}
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
player.name = "Barbara"
|
|
player.score = 0
|
|
|
|
do {
|
|
let row = try player.updateAndFetch(db, columns: [Column("score")], selection: [AllColumns()]) { statement in
|
|
try Row.fetchOne(statement)
|
|
}
|
|
XCTAssertEqual(row, ["id": 1, "name": "Arthur", "score": 0])
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
|
|
func test_updateChangesAndFetch_modify() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateChangesAndFetch(db) {
|
|
$0.name = "Barbara"
|
|
}
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
|
|
do {
|
|
let updatedPlayer = try player.updateChangesAndFetch(db) {
|
|
$0.name = "Barbara"
|
|
}
|
|
XCTAssertNil(updatedPlayer)
|
|
}
|
|
|
|
do {
|
|
let updatedPlayer = try XCTUnwrap(player.updateChangesAndFetch(db) {
|
|
$0.name = "Craig"
|
|
})
|
|
XCTAssertEqual(updatedPlayer.id, 1)
|
|
XCTAssertEqual(updatedPlayer.name, "Craig")
|
|
XCTAssertEqual(updatedPlayer.score, 1000)
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
|
|
func test_updateChangesAndFetch_as_modify() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateChangesAndFetch(db, as: PartialPlayer.self) {
|
|
$0.name = "Barbara"
|
|
}
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
|
|
do {
|
|
let updatedPlayer = try player.updateChangesAndFetch(db, as: PartialPlayer.self) {
|
|
$0.name = "Barbara"
|
|
}
|
|
XCTAssertNil(updatedPlayer)
|
|
}
|
|
|
|
do {
|
|
let updatedPlayer = try XCTUnwrap(player.updateChangesAndFetch(db, as: PartialPlayer.self) {
|
|
$0.name = "Craig"
|
|
})
|
|
XCTAssertEqual(updatedPlayer.id, 1)
|
|
XCTAssertEqual(updatedPlayer.name, "Craig")
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
|
|
func test_updateChangesAndFetch_selection_fetch_modify() 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
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
do {
|
|
_ = try player.updateChangesAndFetch(
|
|
db, selection: [AllColumns()],
|
|
fetch: { statement in try Row.fetchOne(statement) },
|
|
modify: { $0.name = "Barbara" })
|
|
XCTFail("Expected RecordError")
|
|
} catch RecordError.recordNotFound(databaseTableName: "player", key: ["id": 1.databaseValue]) { }
|
|
|
|
try player.insert(db)
|
|
|
|
do {
|
|
let updatedRow = try player.updateChangesAndFetch(
|
|
db, selection: [AllColumns()],
|
|
fetch: { statement in try Row.fetchOne(statement) },
|
|
modify: { $0.name = "Barbara" })
|
|
XCTAssertNil(updatedRow)
|
|
}
|
|
|
|
do {
|
|
let updatedRow = try player.updateChangesAndFetch(
|
|
db, selection: [AllColumns()],
|
|
fetch: { statement in try Row.fetchOne(statement) },
|
|
modify: { $0.name = "Craig" })
|
|
XCTAssertEqual(updatedRow, ["id": 1, "name": "Craig", "score": 1000])
|
|
}
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 2)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 3)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 2)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 2)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Upsert
|
|
|
|
extension MutablePersistableRecordTests {
|
|
func test_upsert() throws {
|
|
#if GRDBCUSTOMSQLITE || GRDBCIPHER
|
|
guard sqlite3_libversion_number() >= 3035000 else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#else
|
|
guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#endif
|
|
|
|
try makeDatabaseQueue().write { db in
|
|
do {
|
|
var player = FullPlayer(name: "Arthur", score: 1000)
|
|
try player.upsert(db)
|
|
|
|
// Test SQL
|
|
XCTAssertEqual(lastSQLQuery, """
|
|
INSERT INTO "player" ("id", "name", "score") \
|
|
VALUES (NULL,'Arthur',1000) \
|
|
ON CONFLICT DO UPDATE SET "name" = "excluded"."name", "score" = "excluded"."score" \
|
|
RETURNING "rowid"
|
|
""")
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, FullPlayer.orderByPrimaryKey())
|
|
XCTAssertEqual(rows, [
|
|
["id": 1, "name": "Arthur", "score":1000],
|
|
])
|
|
|
|
// Test didSave callback
|
|
XCTAssertEqual(player.id, 1)
|
|
|
|
// Test other callbacks
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
|
|
// Test conflict on name
|
|
do {
|
|
// Set the last inserted row id to some arbitrary value
|
|
_ = try FullPlayer(id: 42, name: "Barbara", score: 0).inserted(db)
|
|
XCTAssertNotEqual(db.lastInsertedRowID, 1)
|
|
|
|
var player = FullPlayer(name: "Arthur", score: 100)
|
|
try player.upsert(db)
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, FullPlayer.orderByPrimaryKey())
|
|
XCTAssertEqual(rows, [
|
|
["id": 1, "name": "Arthur", "score":100],
|
|
["id": 42, "name": "Barbara", "score":0],
|
|
])
|
|
|
|
// Test didSave callback
|
|
XCTAssertEqual(player.id, 1)
|
|
}
|
|
|
|
// Test conflict on id
|
|
do {
|
|
var player = FullPlayer(id: 1, name: "Craig", score: 500)
|
|
try player.upsert(db)
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, FullPlayer.orderByPrimaryKey())
|
|
XCTAssertEqual(rows, [
|
|
["id": 1, "name": "Craig", "score":500],
|
|
["id": 42, "name": "Barbara", "score":0],
|
|
])
|
|
|
|
// Test didSave callback
|
|
XCTAssertEqual(player.id, 1)
|
|
}
|
|
|
|
// Test conflict on both id and name (same row)
|
|
do {
|
|
var player = FullPlayer(id: 1, name: "Craig", score: 200)
|
|
try player.upsert(db)
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, FullPlayer.orderByPrimaryKey())
|
|
XCTAssertEqual(rows, [
|
|
["id": 1, "name": "Craig", "score":200],
|
|
["id": 42, "name": "Barbara", "score":0],
|
|
])
|
|
|
|
// Test didSave callback
|
|
XCTAssertEqual(player.id, 1)
|
|
}
|
|
|
|
// Test conflict on both id and name (different rows)
|
|
do {
|
|
var player = FullPlayer(id: 1, name: "Barbara", score: 300)
|
|
|
|
do {
|
|
try player.upsert(db)
|
|
XCTFail("Expected error")
|
|
} catch let error as DatabaseError {
|
|
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
|
|
XCTAssertEqual(error.message, "UNIQUE constraint failed: player.name")
|
|
XCTAssertEqual(error.sql!, """
|
|
INSERT INTO "player" ("id", "name", "score") \
|
|
VALUES (?,?,?) \
|
|
ON CONFLICT DO UPDATE SET "name" = "excluded"."name", "score" = "excluded"."score" \
|
|
RETURNING "rowid"
|
|
""")
|
|
}
|
|
|
|
// Test callbacks
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func test_upsertAndFetch_do_update_set_where() throws {
|
|
#if GRDBCUSTOMSQLITE || GRDBCIPHER
|
|
guard sqlite3_libversion_number() >= 3035000 else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#else
|
|
guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#endif
|
|
|
|
try makeDatabaseQueue().write { db in
|
|
// Test exemples of https://www.sqlite.org/lang_UPSERT.html
|
|
do {
|
|
try db.execute(sql: """
|
|
CREATE TABLE vocabulary(
|
|
word TEXT PRIMARY KEY,
|
|
kind TEXT,
|
|
isTainted BOOLEAN DEFAULT 0,
|
|
count INT DEFAULT 1);
|
|
INSERT INTO vocabulary(word, isTainted) VALUES('jovial', 1);
|
|
""")
|
|
|
|
struct Vocabulary: Decodable, MutablePersistableRecord, FetchableRecord {
|
|
var word: String
|
|
var kind: String
|
|
var isTainted: Bool
|
|
var count: Int?
|
|
var rowID: Int64?
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
// Don't encode count and rowID
|
|
container["word"] = word
|
|
container["kind"] = kind
|
|
container["isTainted"] = isTainted
|
|
}
|
|
|
|
mutating func didInsert(_ inserted: InsertionSuccess) {
|
|
rowID = inserted.rowID
|
|
}
|
|
}
|
|
|
|
// One column with specific assignment (count)
|
|
// One column with no assignment (isTainted)
|
|
// One column with default overwrite assignment (kind)
|
|
do {
|
|
var vocabulary = Vocabulary(word: "jovial", kind: "adjective", isTainted: false)
|
|
let upserted = try vocabulary.upsertAndFetch(
|
|
db, onConflict: ["word"],
|
|
doUpdate: { _ in
|
|
[Column("count") += 1, // increment count
|
|
Column("isTainted").noOverwrite] // don't overwrite isTainted
|
|
})
|
|
|
|
// Test didSave
|
|
XCTAssertEqual(vocabulary.rowID, 1)
|
|
|
|
// Test SQL
|
|
XCTAssertEqual(lastSQLQuery, """
|
|
INSERT INTO "vocabulary" ("word", "kind", "isTainted") \
|
|
VALUES ('jovial','adjective',0) \
|
|
ON CONFLICT("word") \
|
|
DO UPDATE SET "count" = "count" + 1, "kind" = "excluded"."kind" \
|
|
RETURNING *, "rowid"
|
|
""")
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM vocabulary")
|
|
XCTAssertEqual(rows, [
|
|
["word": "jovial", "kind": "adjective", "isTainted": 1, "count": 2],
|
|
])
|
|
|
|
// Test upserted record
|
|
XCTAssertEqual(upserted.word, "jovial")
|
|
XCTAssertEqual(upserted.kind, "adjective")
|
|
XCTAssertEqual(upserted.isTainted, true) // Not overwritten
|
|
XCTAssertEqual(upserted.count, 2) // incremented
|
|
}
|
|
|
|
// All columns with no assignment: make sure we return something
|
|
do {
|
|
var vocabulary = Vocabulary(word: "jovial", kind: "ignored", isTainted: false)
|
|
let upserted = try vocabulary.upsertAndFetch(
|
|
db, onConflict: ["word"],
|
|
doUpdate: { _ in
|
|
[Column("count").noOverwrite,
|
|
Column("isTainted").noOverwrite,
|
|
Column("kind").noOverwrite]
|
|
})
|
|
|
|
// Test didSave
|
|
XCTAssertEqual(vocabulary.rowID, 1)
|
|
|
|
// Test SQL (the DO UPDATE clause is not empty, so that the
|
|
// RETURNING clause could return something).
|
|
XCTAssertEqual(lastSQLQuery, """
|
|
INSERT INTO "vocabulary" ("word", "kind", "isTainted") \
|
|
VALUES ('jovial','ignored',0) \
|
|
ON CONFLICT("word") \
|
|
DO UPDATE SET "word" = "word" \
|
|
RETURNING *, "rowid"
|
|
""")
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM vocabulary")
|
|
XCTAssertEqual(rows, [
|
|
["word": "jovial", "kind": "adjective", "isTainted": 1, "count": 2],
|
|
])
|
|
|
|
// Test upserted record
|
|
XCTAssertEqual(upserted.word, "jovial")
|
|
XCTAssertEqual(upserted.kind, "adjective")
|
|
XCTAssertEqual(upserted.isTainted, true)
|
|
XCTAssertEqual(upserted.count, 2)
|
|
}
|
|
}
|
|
|
|
do {
|
|
try db.execute(sql: """
|
|
CREATE TABLE phonebook(name TEXT PRIMARY KEY, phonenumber TEXT);
|
|
INSERT INTO phonebook(name,phonenumber) VALUES('Alice','ignored');
|
|
""")
|
|
|
|
struct Phonebook: Codable, MutablePersistableRecord, FetchableRecord {
|
|
var name: String
|
|
var phonenumber: String
|
|
}
|
|
|
|
var phonebook = Phonebook(name: "Alice", phonenumber: "704-555-1212")
|
|
let upserted = try phonebook.upsertAndFetch(
|
|
db, onConflict: ["name"],
|
|
doUpdate: { excluded in
|
|
[Column("phonenumber").set(to: excluded["phonenumber"])]
|
|
})
|
|
|
|
// Test SQL
|
|
XCTAssertEqual(lastSQLQuery, """
|
|
INSERT INTO "phonebook" ("name", "phonenumber") \
|
|
VALUES ('Alice','704-555-1212') \
|
|
ON CONFLICT("name") DO UPDATE SET "phonenumber" = "excluded"."phonenumber" \
|
|
RETURNING *, "rowid"
|
|
""")
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM phonebook")
|
|
XCTAssertEqual(rows, [
|
|
["name": "Alice", "phonenumber": "704-555-1212"],
|
|
])
|
|
|
|
// Test upserted record
|
|
XCTAssertEqual(upserted.name, "Alice")
|
|
XCTAssertEqual(upserted.phonenumber, "704-555-1212")
|
|
}
|
|
}
|
|
}
|
|
|
|
func test_upsertAndFetch() throws {
|
|
#if GRDBCUSTOMSQLITE || GRDBCIPHER
|
|
guard sqlite3_libversion_number() >= 3035000 else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#else
|
|
guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#endif
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var player = FullPlayer(id: 1, name: "Arthur", score: 1000)
|
|
let upsertedPlayer = try player.upsertAndFetch(db)
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name", "score") \
|
|
VALUES (1,'Arthur',1000) \
|
|
ON CONFLICT DO UPDATE SET "name" = "excluded"."name", "score" = "excluded"."score" \
|
|
RETURNING *, "rowid"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, FullPlayer.orderByPrimaryKey())
|
|
XCTAssertEqual(rows, [
|
|
["id": 1, "name": "Arthur", "score":1000],
|
|
])
|
|
|
|
XCTAssertEqual(player.id, 1)
|
|
XCTAssertEqual(upsertedPlayer.id, 1)
|
|
XCTAssertEqual(upsertedPlayer.name, "Arthur")
|
|
XCTAssertEqual(upsertedPlayer.score, 1000)
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var player = FullPlayer(id: 1, name: "Barbara", score: 100)
|
|
let upsertedPlayer = try player.upsertAndFetch(db)
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name", "score") \
|
|
VALUES (1,'Barbara',100) \
|
|
ON CONFLICT DO UPDATE SET "name" = "excluded"."name", "score" = "excluded"."score" \
|
|
RETURNING *, "rowid"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
// Test database state
|
|
let rows = try Row.fetchAll(db, FullPlayer.orderByPrimaryKey())
|
|
XCTAssertEqual(rows, [
|
|
["id": 1, "name": "Barbara", "score":100],
|
|
])
|
|
|
|
XCTAssertEqual(player.id, 1)
|
|
XCTAssertEqual(upsertedPlayer.id, 1)
|
|
XCTAssertEqual(upsertedPlayer.name, "Barbara")
|
|
XCTAssertEqual(upsertedPlayer.score, 100)
|
|
|
|
XCTAssertEqual(player.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(player.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(player.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(player.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(player.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(player.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(player.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
func test_upsertAndFetch_as() throws {
|
|
#if GRDBCUSTOMSQLITE || GRDBCIPHER
|
|
guard sqlite3_libversion_number() >= 3035000 else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#else
|
|
guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else {
|
|
throw XCTSkip("UPSERT is not available")
|
|
}
|
|
#endif
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
do {
|
|
sqlQueries.removeAll()
|
|
var partialPlayer = PartialPlayer(name: "Arthur")
|
|
let fullPlayer = try partialPlayer.upsertAndFetch(db, as: FullPlayer.self)
|
|
|
|
XCTAssert(sqlQueries.contains("""
|
|
INSERT INTO "player" ("id", "name") \
|
|
VALUES (NULL,'Arthur') \
|
|
ON CONFLICT DO UPDATE SET "name" = "excluded"."name" \
|
|
RETURNING *, "rowid"
|
|
"""), sqlQueries.joined(separator: "\n"))
|
|
|
|
XCTAssertEqual(partialPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.id, 1)
|
|
XCTAssertEqual(fullPlayer.name, "Arthur")
|
|
XCTAssertEqual(fullPlayer.score, 1000)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willInsertCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundInsertExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didInsertCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willUpdateCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundUpdateExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didUpdateCount, 0)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willSaveCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveEnterCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundSaveExitCount, 1)
|
|
XCTAssertEqual(partialPlayer.callbacks.didSaveCount, 1)
|
|
|
|
XCTAssertEqual(partialPlayer.callbacks.willDeleteCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteEnterCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.aroundDeleteExitCount, 0)
|
|
XCTAssertEqual(partialPlayer.callbacks.didDeleteCount, 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Callback Misuse
|
|
|
|
extension MutablePersistableRecordTests {
|
|
func test_aroundSave_misuse_by_not_calling_the_action() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
|
|
// It is a programmer error to not call the `save` argument
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: "CREATE TABLE badRecord(a)")
|
|
try BadRecord().update(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundSave_misuse_by_not_rethrowing_the_action_error() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
|
|
// It is a programmer error to not rethrow the error of the `save` argument
|
|
_ = try? save()
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: "CREATE TABLE badRecord(a)")
|
|
// Fails because we can't update anything
|
|
try BadRecord().update(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundUpdate_misuse_by_not_calling_the_action() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
|
|
// It is a programmer error to not call the `update` argument
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: "CREATE TABLE badRecord(a)")
|
|
try BadRecord().update(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundUpdate_misuse_by_not_rethrowing_the_action_error() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
|
|
// It is a programmer error to not rethrow the error of the `update` argument
|
|
_ = try? update()
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: "CREATE TABLE badRecord(a)")
|
|
// Fails because we can't update anything
|
|
try BadRecord().update(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundInsert_misuse_by_not_calling_the_action() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
// It is a programmer error to not call the `insert` argument
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: "CREATE TABLE badRecord(a)")
|
|
var record = BadRecord()
|
|
try record.insert(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundInsert_misuse_by_not_rethrowing_the_action_error() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
// It is a programmer error to not rethrow the error of the `insert` argument
|
|
_ = try? insert()
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: """
|
|
CREATE TABLE badRecord(id INTEGER PRIMARY KEY);
|
|
INSERT INTO badRecord (id) VALUES (1);
|
|
""")
|
|
var record = BadRecord()
|
|
// Fails because we insert a conflict
|
|
try record.insert(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundDelete_misuse_by_not_calling_the_action() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
|
|
// It is a programmer error to not call the `delete` argument
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: "CREATE TABLE badRecord(a)")
|
|
try BadRecord().delete(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
|
|
func test_aroundDelete_misuse_by_not_rethrowing_the_action_error() throws {
|
|
struct BadRecord: MutablePersistableRecord, Encodable {
|
|
let id = 1
|
|
func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
|
|
// It is a programmer error to not rethrow the error of the `delete` argument
|
|
_ = try? delete()
|
|
}
|
|
}
|
|
do {
|
|
try makeDatabaseQueue().write { db in
|
|
try db.execute(sql: """
|
|
CREATE TABLE badRecord(id INTEGER PRIMARY KEY);
|
|
CREATE TABLE child(id INTEGER PRIMARY KEY REFERENCES badRecord(id) ON DELETE RESTRICT);
|
|
INSERT INTO badRecord (id) VALUES (1);
|
|
INSERT INTO child (id) VALUES (1);
|
|
""")
|
|
// Fails because this deletion violates a constraint
|
|
try BadRecord().delete(db)
|
|
XCTFail("Expected SQLITE_MISUSE error")
|
|
}
|
|
} catch DatabaseError.SQLITE_MISUSE { }
|
|
}
|
|
}
|