2118 lines
91 KiB
Swift
2118 lines
91 KiB
Swift
import XCTest
|
|
import GRDB
|
|
|
|
private struct PersistableRecordPerson : PersistableRecord {
|
|
var name: String?
|
|
var age: Int?
|
|
|
|
static let databaseTableName = "persons"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["name"] = name
|
|
container["age"] = age
|
|
}
|
|
}
|
|
|
|
private class PersistableRecordPersonClass : PersistableRecord {
|
|
var id: Int64?
|
|
var name: String?
|
|
var age: Int?
|
|
|
|
init(id: Int64?, name: String?, age: Int?) {
|
|
self.id = id
|
|
self.name = name
|
|
self.age = age
|
|
}
|
|
|
|
static let databaseTableName = "persons"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
// mangle case
|
|
container["ID"] = id
|
|
container["naME"] = name
|
|
container["Age"] = age
|
|
}
|
|
|
|
func didInsert(_ inserted: InsertionSuccess) {
|
|
id = inserted.rowID
|
|
}
|
|
}
|
|
|
|
private struct PersistableRecordCountry : PersistableRecord {
|
|
var isoCode: String
|
|
var name: String
|
|
|
|
static let databaseTableName = "countries"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["isoCode"] = isoCode
|
|
container["name"] = name
|
|
}
|
|
}
|
|
|
|
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 PersistableRecordCustomizedCountry : PersistableRecord {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func didInsert(_ inserted: InsertionSuccess) {
|
|
callbacks.didInsertCount += 1
|
|
}
|
|
|
|
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 Citizenship : PersistableRecord {
|
|
let personID: Int64
|
|
let countryIsoCode: String
|
|
|
|
static let databaseTableName = "citizenships"
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
container["countryIsoCode"] = countryIsoCode
|
|
container["personID"] = personID
|
|
}
|
|
}
|
|
|
|
private struct PartialPlayer: Codable, PersistableRecord, FetchableRecord {
|
|
static let databaseTableName = "player"
|
|
let callbacks = Callbacks()
|
|
var id: Int64?
|
|
var name: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id, name
|
|
}
|
|
|
|
func willInsert(_ db: Database) throws {
|
|
callbacks.willInsertCount += 1
|
|
}
|
|
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
callbacks.aroundInsertEnterCount += 1
|
|
_ = try insert()
|
|
callbacks.aroundInsertExitCount += 1
|
|
}
|
|
|
|
func didInsert(_ inserted: InsertionSuccess) {
|
|
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, PersistableRecord, 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()
|
|
|
|
func willInsert(_ db: Database) throws {
|
|
callbacks.willInsertCount += 1
|
|
}
|
|
|
|
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
|
callbacks.aroundInsertEnterCount += 1
|
|
_ = try insert()
|
|
callbacks.aroundInsertExitCount += 1
|
|
}
|
|
|
|
func didInsert(_ inserted: InsertionSuccess) {
|
|
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 PersistableRecordTests: 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 TEXT NOT NULL,
|
|
age INT NOT NULL);
|
|
CREATE TABLE countries (
|
|
isoCode TEXT NOT NULL PRIMARY KEY,
|
|
name TEXT NOT NULL);
|
|
CREATE TABLE citizenships (
|
|
countryIsoCode TEXT NOT NULL REFERENCES countries(isoCode),
|
|
personID INTEGER NOT NULL REFERENCES persons(id));
|
|
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: - PersistableRecordPerson
|
|
|
|
func testInsertPersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person = PersistableRecordPerson(name: "Arthur", age: 42)
|
|
try person.insert(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
}
|
|
}
|
|
|
|
func testSavePersistableRecordPerson() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person = PersistableRecordPerson(name: "Arthur", age: 42)
|
|
try person.save(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM persons")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["name"] as String, "Arthur")
|
|
}
|
|
}
|
|
|
|
// MARK: - PersistableRecordPersonClass
|
|
|
|
func testInsertPersistableRecordPersonClass() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 42)
|
|
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 testUpdatePersistableRecordPersonClass() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person1 = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 42)
|
|
try person1.insert(db)
|
|
let person2 = PersistableRecordPersonClass(id: nil, name: "Barbara", age: 39)
|
|
try person2.insert(db)
|
|
|
|
person1.name = "Craig"
|
|
try person1.update(db)
|
|
XCTAssertTrue([
|
|
"UPDATE \"persons\" SET \"age\"=42, \"name\"='Craig' WHERE \"id\"=1",
|
|
"UPDATE \"persons\" SET \"name\"='Craig', \"age\"=42 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 testPartialUpdatePersistableRecordPersonClass() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person1 = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 24)
|
|
try person1.insert(db)
|
|
let person2 = PersistableRecordPersonClass(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 testSavePersistableRecordPersonClass() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person1 = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 42)
|
|
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")
|
|
|
|
let person2 = PersistableRecordPersonClass(id: nil, name: "Barbara", age: 39)
|
|
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 testDeletePersistableRecordPersonClass() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person1 = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 42)
|
|
try person1.insert(db)
|
|
let person2 = PersistableRecordPersonClass(id: nil, name: "Barbara", age: 39)
|
|
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 testExistsPersistableRecordPersonClass() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 42)
|
|
try person.insert(db)
|
|
XCTAssertTrue(try person.exists(db))
|
|
|
|
try person.delete(db)
|
|
XCTAssertFalse(try person.exists(db))
|
|
}
|
|
}
|
|
|
|
// MARK: - PersistableRecordCountry
|
|
|
|
func testInsertPersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country = PersistableRecordCountry(isoCode: "FR", name: "France")
|
|
try country.insert(db)
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
}
|
|
}
|
|
|
|
func testUpdatePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = PersistableRecordCountry(isoCode: "FR", name: "France")
|
|
try country1.insert(db)
|
|
let country2 = PersistableRecordCountry(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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testPartialUpdatePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = PersistableRecordCountry(isoCode: "FR", name: "France")
|
|
try country1.insert(db)
|
|
let country2 = PersistableRecordCountry(isoCode: "US", name: "United States")
|
|
try country2.insert(db)
|
|
|
|
do {
|
|
country1.name = "France Métropolitaine"
|
|
try country1.update(db, columns: [String]())
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"isoCode\"='FR' WHERE \"isoCode\"='FR'")
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
|
|
do {
|
|
country1.name = "France Métropolitaine"
|
|
try country1.update(db, columns: [Column("name")])
|
|
XCTAssertEqual(self.lastSQLQuery, "UPDATE \"countries\" SET \"name\"='France Métropolitaine' WHERE \"isoCode\"='FR'")
|
|
|
|
let rows = try Row.fetchAll(db, sql: "SELECT * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testSavePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = PersistableRecordCountry(isoCode: "FR", name: "France")
|
|
try country1.save(db)
|
|
|
|
var rows = try Row.fetchAll(db, sql: "SELECT * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
|
|
let country2 = PersistableRecordCountry(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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
|
|
try country1.delete(db)
|
|
try country1.save(db)
|
|
|
|
rows = try Row.fetchAll(db, sql: "SELECT * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testDeletePersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country1 = PersistableRecordCountry(isoCode: "FR", name: "France")
|
|
try country1.insert(db)
|
|
let country2 = PersistableRecordCountry(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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testExistsPersistableRecordCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country = PersistableRecordCountry(isoCode: "FR", name: "France")
|
|
try country.insert(db)
|
|
XCTAssertTrue(try country.exists(db))
|
|
|
|
try country.delete(db)
|
|
XCTAssertFalse(try country.exists(db))
|
|
}
|
|
}
|
|
|
|
// MARK: - PersistableRecordCustomizedCountry
|
|
|
|
func testInsertPersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country = PersistableRecordCustomizedCountry(
|
|
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 * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
}
|
|
}
|
|
|
|
func testUpdatePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = PersistableRecordCustomizedCountry(
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country1.insert(db)
|
|
let country2 = PersistableRecordCustomizedCountry(
|
|
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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testRecordErrorPersistableRecordCustomizedCountry() throws {
|
|
let country = PersistableRecordCustomizedCountry(
|
|
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 testPartialUpdatePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = PersistableRecordCustomizedCountry(
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country1.insert(db)
|
|
let country2 = PersistableRecordCustomizedCountry(
|
|
isoCode: "US",
|
|
name: "United States")
|
|
try country2.insert(db)
|
|
|
|
country1.name = "France Métropolitaine"
|
|
try country1.update(db, columns: ["name"])
|
|
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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testSavePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
var country1 = PersistableRecordCustomizedCountry(
|
|
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 * FROM countries")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France")
|
|
|
|
let country2 = PersistableRecordCustomizedCountry(
|
|
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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 2)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "FR")
|
|
XCTAssertEqual(rows[0]["name"] as String, "France Métropolitaine")
|
|
XCTAssertEqual(rows[1]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[1]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testDeletePersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country1 = PersistableRecordCustomizedCountry(
|
|
isoCode: "FR",
|
|
name: "France")
|
|
try country1.insert(db)
|
|
let country2 = PersistableRecordCustomizedCountry(
|
|
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 * FROM countries ORDER BY isoCode")
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0]["isoCode"] as String, "US")
|
|
XCTAssertEqual(rows[0]["name"] as String, "United States")
|
|
}
|
|
}
|
|
|
|
func testExistsPersistableRecordCustomizedCountry() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let country = PersistableRecordCustomizedCountry(
|
|
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: - Errors
|
|
|
|
func testInsertErrorDoesNotPreventSubsequentInserts() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let person = PersistableRecordPersonClass(id: nil, name: "Arthur", age: 12)
|
|
try person.insert(db)
|
|
try PersistableRecordCountry(isoCode: "FR", name: "France").insert(db)
|
|
do {
|
|
try Citizenship(personID: person.id!, countryIsoCode: "US").insert(db)
|
|
} catch let error as DatabaseError {
|
|
XCTAssertEqual(error.resultCode, .SQLITE_CONSTRAINT)
|
|
}
|
|
try Citizenship(personID: person.id!, countryIsoCode: "FR").insert(db)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Custom nested Codable types - nested saved as JSON
|
|
|
|
extension PersistableRecordTests {
|
|
|
|
func testOptionalNestedStruct() throws {
|
|
struct NestedStruct : Codable {
|
|
let firstName: String?
|
|
let lastName: String?
|
|
}
|
|
|
|
struct StructWithNestedType : PersistableRecord, Codable {
|
|
static let databaseTableName = "t1"
|
|
let nested: NestedStruct?
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("nested", .text)
|
|
}
|
|
|
|
let nested = NestedStruct(firstName: "Bob", lastName: "Dylan")
|
|
let value = StructWithNestedType(nested: nested)
|
|
try value.insert(db)
|
|
|
|
let dbValue = try DatabaseValue.fetchOne(db, sql: "SELECT nested FROM t1")!
|
|
|
|
// Encodable has a default implementation which encodes a model to JSON as String.
|
|
// We expect here JSON in the form of a String
|
|
XCTAssert(dbValue.storage.value is String)
|
|
let string = dbValue.storage.value as! String
|
|
if let data = string.data(using: .utf8) {
|
|
do {
|
|
let decoded = try JSONDecoder().decode(NestedStruct.self, from: data)
|
|
XCTAssertEqual(nested.firstName, decoded.firstName)
|
|
XCTAssertEqual(nested.lastName, decoded.lastName)
|
|
} catch {
|
|
XCTFail(error.localizedDescription)
|
|
}
|
|
} else {
|
|
XCTFail("Failed to convert " + string)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testOptionalNestedStructNil() throws {
|
|
struct NestedStruct : Encodable {
|
|
let firstName: String?
|
|
let lastName: String?
|
|
}
|
|
|
|
struct StructWithNestedType : PersistableRecord, Encodable {
|
|
static let databaseTableName = "t1"
|
|
let nested: NestedStruct?
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("nested", .text)
|
|
}
|
|
|
|
let value = StructWithNestedType(nested: nil)
|
|
try value.insert(db)
|
|
|
|
let dbValue = try DatabaseValue.fetchOne(db, sql: "SELECT nested FROM t1")!
|
|
|
|
// We expect here nil
|
|
XCTAssertNil(dbValue.storage.value)
|
|
}
|
|
}
|
|
|
|
func testOptionalNestedArrayStruct() throws {
|
|
struct NestedStruct : Codable {
|
|
let firstName: String?
|
|
let lastName: String?
|
|
}
|
|
|
|
struct StructWithNestedType : PersistableRecord, Codable {
|
|
static let databaseTableName = "t1"
|
|
let nested: [NestedStruct]?
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("nested", .text)
|
|
}
|
|
|
|
let nested = NestedStruct(firstName: "Bob", lastName: "Dylan")
|
|
let value = StructWithNestedType(nested: [nested, nested])
|
|
try value.insert(db)
|
|
|
|
let dbValue = try DatabaseValue.fetchOne(db, sql: "SELECT nested FROM t1")!
|
|
|
|
// Encodable has a default implementation which encodes a model to JSON as String.
|
|
// We expect here JSON in the form of a String
|
|
XCTAssert(dbValue.storage.value is String)
|
|
let string = dbValue.storage.value as! String
|
|
if let data = string.data(using: .utf8) {
|
|
do {
|
|
let decoded = try JSONDecoder().decode([NestedStruct].self, from: data)
|
|
XCTAssertEqual(decoded.count, 2)
|
|
XCTAssertEqual(nested.firstName, decoded.first!.firstName)
|
|
XCTAssertEqual(nested.lastName, decoded.first!.lastName)
|
|
XCTAssertEqual(nested.firstName, decoded.last!.firstName)
|
|
XCTAssertEqual(nested.lastName, decoded.last!.lastName)
|
|
} catch {
|
|
XCTFail(error.localizedDescription)
|
|
}
|
|
} else {
|
|
XCTFail("Failed to convert " + string)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testOptionalNestedArrayStructNil() throws {
|
|
struct NestedStruct : Encodable {
|
|
let firstName: String?
|
|
let lastName: String?
|
|
}
|
|
|
|
struct StructWithNestedType : PersistableRecord, Encodable {
|
|
static let databaseTableName = "t1"
|
|
let nested: [NestedStruct]?
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("nested", .text)
|
|
}
|
|
|
|
let value = StructWithNestedType(nested: nil)
|
|
try value.insert(db)
|
|
|
|
let dbValue = try DatabaseValue.fetchOne(db, sql: "SELECT nested FROM t1")!
|
|
|
|
// We expect here nil
|
|
XCTAssertNil(dbValue.storage.value)
|
|
}
|
|
}
|
|
|
|
func testNonOptionalNestedStruct() throws {
|
|
struct NestedStruct : Codable {
|
|
let firstName: String
|
|
let lastName: String
|
|
}
|
|
|
|
struct StructWithNestedType : PersistableRecord, Codable {
|
|
static let databaseTableName = "t1"
|
|
let nested: NestedStruct
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("nested", .text)
|
|
}
|
|
|
|
let nested = NestedStruct(firstName: "Bob", lastName: "Dylan")
|
|
let value = StructWithNestedType(nested: nested)
|
|
try value.insert(db)
|
|
|
|
let dbValue = try DatabaseValue.fetchOne(db, sql: "SELECT nested FROM t1")!
|
|
|
|
// Encodable has a default implementation which encodes a model to JSON as String.
|
|
// We expect here JSON in the form of a String
|
|
XCTAssert(dbValue.storage.value is String)
|
|
let string = dbValue.storage.value as! String
|
|
if let data = string.data(using: .utf8) {
|
|
do {
|
|
let decoded = try JSONDecoder().decode(NestedStruct.self, from: data)
|
|
XCTAssertEqual(nested.firstName, decoded.firstName)
|
|
XCTAssertEqual(nested.lastName, decoded.lastName)
|
|
} catch {
|
|
XCTFail(error.localizedDescription)
|
|
}
|
|
} else {
|
|
XCTFail("Failed to convert " + string)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testNonOptionalNestedArrayStruct() throws {
|
|
struct NestedStruct : Codable {
|
|
let firstName: String
|
|
let lastName: String
|
|
}
|
|
|
|
struct StructWithNestedType : PersistableRecord, Codable {
|
|
static let databaseTableName = "t1"
|
|
let nested: [NestedStruct]
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("nested", .text)
|
|
}
|
|
|
|
let nested = NestedStruct(firstName: "Bob", lastName: "Dylan")
|
|
let value = StructWithNestedType(nested: [nested])
|
|
try value.insert(db)
|
|
|
|
let dbValue = try DatabaseValue.fetchOne(db, sql: "SELECT nested FROM t1")!
|
|
|
|
// Encodable has a default implementation which encodes a model to JSON as String.
|
|
// We expect here JSON in the form of a String
|
|
XCTAssert(dbValue.storage.value is String)
|
|
let string = dbValue.storage.value as! String
|
|
if let data = string.data(using: .utf8) {
|
|
do {
|
|
let decoded = try JSONDecoder().decode([NestedStruct].self, from: data)
|
|
XCTAssertEqual(nested.firstName, decoded.first!.firstName)
|
|
XCTAssertEqual(nested.lastName, decoded.first!.lastName)
|
|
} catch {
|
|
XCTFail(error.localizedDescription)
|
|
}
|
|
} else {
|
|
XCTFail("Failed to convert " + string)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testStringStoredInArray() throws {
|
|
struct TestStruct : PersistableRecord, FetchableRecord, Codable {
|
|
static let databaseTableName = "t1"
|
|
let numbers: [String]
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("numbers", .text)
|
|
}
|
|
|
|
let model = TestStruct(numbers: ["test1", "test2", "test3"])
|
|
try model.insert(db)
|
|
|
|
// Encodable has a default implementation which encodes a model to JSON as String.
|
|
// We expect here JSON in the form of a String
|
|
|
|
guard let fetchModel = try TestStruct.fetchOne(db) else {
|
|
XCTFail("Could not find record in db")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(model.numbers, fetchModel.numbers)
|
|
}
|
|
}
|
|
|
|
func testOptionalStringStoredInArray() throws {
|
|
struct TestStruct : PersistableRecord, FetchableRecord, Codable {
|
|
static let databaseTableName = "t1"
|
|
let numbers: [String]?
|
|
}
|
|
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
try db.create(table: "t1") { t in
|
|
t.column("numbers", .text)
|
|
}
|
|
|
|
let model = TestStruct(numbers: ["test1", "test2", "test3"])
|
|
try model.insert(db)
|
|
|
|
// Encodable has a default implementation which encodes a model to JSON as String.
|
|
// We expect here JSON in the form of a String
|
|
|
|
guard let fetchModel = try TestStruct.fetchOne(db) else {
|
|
XCTFail("Could not find record in db")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(model.numbers, fetchModel.numbers)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Insert and Fetch
|
|
|
|
extension PersistableRecordTests {
|
|
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()
|
|
let 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(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()
|
|
let 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(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: - Save and Fetch
|
|
|
|
extension PersistableRecordTests {
|
|
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()
|
|
let 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(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 {
|
|
let 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(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()
|
|
let 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(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()
|
|
let 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(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 {
|
|
let 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(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()
|
|
let 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(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: - Upsert
|
|
|
|
extension PersistableRecordTests {
|
|
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 {
|
|
let 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 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)
|
|
|
|
let 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 conflict on id
|
|
do {
|
|
let 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 conflict on both id and name (same row)
|
|
do {
|
|
let 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 conflict on both id and name (different rows)
|
|
do {
|
|
let 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, PersistableRecord, FetchableRecord {
|
|
var word: String
|
|
var kind: String
|
|
var isTainted: Bool
|
|
var count: Int?
|
|
|
|
func encode(to container: inout PersistenceContainer) {
|
|
// Don't encode count
|
|
container["word"] = word
|
|
container["kind"] = kind
|
|
container["isTainted"] = isTainted
|
|
}
|
|
}
|
|
|
|
// One column with specific assignment (count)
|
|
// One column with no assignment (isTainted)
|
|
// One column with default overwrite assignment (kind)
|
|
do {
|
|
let 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 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 {
|
|
let 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 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, PersistableRecord, FetchableRecord {
|
|
var name: String
|
|
var phonenumber: String
|
|
}
|
|
|
|
let 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()
|
|
let 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()
|
|
let 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()
|
|
let 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(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)
|
|
}
|
|
}
|
|
}
|
|
}
|