add iOS
This commit is contained in:
@@ -0,0 +1,585 @@
|
||||
import XCTest
|
||||
import GRDB
|
||||
|
||||
func insertItem(_ db: Database, name: String) throws {
|
||||
try db.execute(sql: "INSERT INTO items (name) VALUES (?)", arguments: [name])
|
||||
}
|
||||
|
||||
func fetchAllItemNames(_ dbReader: some DatabaseReader) throws -> [String] {
|
||||
try dbReader.read { db in
|
||||
try String.fetchAll(db, sql: "SELECT * FROM items ORDER BY name")
|
||||
}
|
||||
}
|
||||
|
||||
private class Observer : TransactionObserver {
|
||||
var allRecordedEvents: [DatabaseEvent] = []
|
||||
|
||||
func reset() {
|
||||
allRecordedEvents.removeAll()
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
allRecordedPreUpdateEvents.removeAll()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
var allRecordedPreUpdateEvents: [DatabasePreUpdateEvent] = []
|
||||
func databaseWillChange(with event: DatabasePreUpdateEvent) {
|
||||
allRecordedPreUpdateEvents.append(event.copy())
|
||||
}
|
||||
#endif
|
||||
|
||||
func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { true }
|
||||
|
||||
func databaseDidChange(with event: DatabaseEvent) {
|
||||
allRecordedEvents.append(event.copy())
|
||||
}
|
||||
|
||||
func databaseDidCommit(_ db: Database) {
|
||||
}
|
||||
|
||||
func databaseDidRollback(_ db: Database) {
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseSavepointTests: GRDBTestCase {
|
||||
|
||||
override func setup(_ dbWriter: some DatabaseWriter) throws {
|
||||
try dbWriter.write { db in
|
||||
try db.execute(sql: "CREATE TABLE items (name TEXT)")
|
||||
}
|
||||
}
|
||||
|
||||
func testIsInsideTransaction() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try db.inTransaction {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
|
||||
try db.execute(sql: "BEGIN DEFERRED TRANSACTION")
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try db.execute(sql: "COMMIT") // does not trigger sqlite3_commit_hook, because transaction was not open yet.
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
|
||||
try db.execute(sql: "BEGIN DEFERRED TRANSACTION")
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try db.execute(sql: "ROLLBACK") // does trigger sqlite3_rollback_hook
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
|
||||
try db.execute(sql: "BEGIN IMMEDIATE TRANSACTION")
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try db.execute(sql: "COMMIT") // does trigger sqlite3_commit_hook
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
|
||||
try db.execute(sql: "BEGIN IMMEDIATE TRANSACTION")
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try db.execute(sql: "ROLLBACK")
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
}
|
||||
}
|
||||
|
||||
func testIsInsideTransactionWithImplicitRollback() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try db.create(table: "test") { t in
|
||||
t.column("value", .integer).unique()
|
||||
}
|
||||
try db.execute(sql: "BEGIN TRANSACTION")
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try db.execute(sql: "INSERT INTO test (value) VALUES (?)", arguments: [1])
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
XCTAssertThrowsError(try db.execute(sql: "INSERT OR ROLLBACK INTO test (value) VALUES (?)", arguments: [1]))
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
XCTAssertThrowsError(try db.execute(sql: "COMMIT"))
|
||||
}
|
||||
}
|
||||
|
||||
func testReleaseTopLevelSavepointFromDatabaseWithDefaultDeferredTransactions() throws {
|
||||
dbConfiguration.defaultTransactionKind = .deferred
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
}
|
||||
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN DEFERRED TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"COMMIT TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item3')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 3)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testRollbackTopLevelSavepointFromDatabaseWithDefaultDeferredTransactions() throws {
|
||||
dbConfiguration.defaultTransactionKind = .deferred
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN DEFERRED TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"ROLLBACK TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item3')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item3"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 3)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testNestedSavepointFromDatabaseWithDefaultDeferredTransactions() throws {
|
||||
dbConfiguration.defaultTransactionKind = .deferred
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN DEFERRED TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"COMMIT TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3", "item4", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 5)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN DEFERRED TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"ROLLBACK TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 5)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN DEFERRED TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"ROLLBACK TRANSACTION TO SAVEPOINT grdb",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"COMMIT TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item4", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 4)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN DEFERRED TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"ROLLBACK TRANSACTION TO SAVEPOINT grdb",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"ROLLBACK TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 4)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
}
|
||||
|
||||
func testReleaseTopLevelSavepointFromDatabaseWithDefaultImmediateTransactions() throws {
|
||||
dbConfiguration.defaultTransactionKind = .immediate
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN IMMEDIATE TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"COMMIT TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item3')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 3)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testRollbackTopLevelSavepointFromDatabaseWithDefaultImmediateTransactions() throws {
|
||||
dbConfiguration.defaultTransactionKind = .immediate
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN IMMEDIATE TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"ROLLBACK TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item3')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item3"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 3)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 3)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testNestedSavepointFromDatabaseWithDefaultImmediateTransactions() throws {
|
||||
dbConfiguration.defaultTransactionKind = .immediate
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN IMMEDIATE TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"COMMIT TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item3", "item4", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 5)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN IMMEDIATE TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"ROLLBACK TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 5)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 5)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .commit
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN IMMEDIATE TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"ROLLBACK TRANSACTION TO SAVEPOINT grdb",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"COMMIT TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item2", "item4", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 4)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
|
||||
sqlQueries.removeAll()
|
||||
try dbQueue.writeWithoutTransaction { db in
|
||||
try insertItem(db, name: "item1")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item2")
|
||||
try db.inSavepoint {
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item3")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertTrue(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item4")
|
||||
return .rollback
|
||||
}
|
||||
XCTAssertFalse(db.isInsideTransaction)
|
||||
try insertItem(db, name: "item5")
|
||||
}
|
||||
XCTAssertEqual(sqlQueries, [
|
||||
"INSERT INTO items (name) VALUES ('item1')",
|
||||
"BEGIN IMMEDIATE TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item2')",
|
||||
"SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item3')",
|
||||
"ROLLBACK TRANSACTION TO SAVEPOINT grdb",
|
||||
"RELEASE SAVEPOINT grdb",
|
||||
"INSERT INTO items (name) VALUES ('item4')",
|
||||
"ROLLBACK TRANSACTION",
|
||||
"INSERT INTO items (name) VALUES ('item5')"
|
||||
])
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item1", "item5"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 4)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 4)
|
||||
#endif
|
||||
try dbQueue.inDatabase { db in try db.execute(sql: "DELETE FROM items") }
|
||||
observer.reset()
|
||||
}
|
||||
|
||||
func testSubsequentSavepoints() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
try dbQueue.inTransaction { db in
|
||||
try db.inSavepoint {
|
||||
try insertItem(db, name: "item1")
|
||||
return .rollback
|
||||
}
|
||||
|
||||
try db.inSavepoint {
|
||||
try insertItem(db, name: "item2")
|
||||
return .commit
|
||||
}
|
||||
|
||||
return .commit
|
||||
}
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item2"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 1)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 1)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testSubsequentSavepointsWithErrors() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
let observer = Observer()
|
||||
dbQueue.add(transactionObserver: observer)
|
||||
try dbQueue.inTransaction { db in
|
||||
do {
|
||||
try db.inSavepoint {
|
||||
try insertItem(db, name: "item1")
|
||||
throw DatabaseError(resultCode: ResultCode(rawValue: 123))
|
||||
}
|
||||
XCTFail()
|
||||
} catch let error as DatabaseError {
|
||||
XCTAssertEqual(error.resultCode.rawValue, 123)
|
||||
}
|
||||
|
||||
try db.inSavepoint {
|
||||
try insertItem(db, name: "item2")
|
||||
return .commit
|
||||
}
|
||||
|
||||
return .commit
|
||||
}
|
||||
XCTAssertEqual(try fetchAllItemNames(dbQueue), ["item2"])
|
||||
XCTAssertEqual(observer.allRecordedEvents.count, 1)
|
||||
#if SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
XCTAssertEqual(observer.allRecordedPreUpdateEvents.count, 1)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user