import XCTest import GRDB class TruncateOptimizationTests: GRDBTestCase { // https://www.sqlite.org/c3ref/update_hook.html // // > In the current implementation, the update hook is not invoked [...] // > when rows are deleted using the truncate optimization. // // https://www.sqlite.org/lang_delete.html#truncateopt // // > When the WHERE is omitted from a DELETE statement and the table // > being deleted has no triggers, SQLite uses an optimization to erase // > the entire table content without having to visit each row of the // > table individually. // // We will thus test that the truncate optimization does not prevent // transaction observers from observing individual deletions. // // But that's not enough: preventing the truncate optimization requires GRDB // to fiddle with sqlite3_set_authorizer. When badly done, this can prevent // DROP TABLE statements from dropping tables. SQLite3 authorizers are // invoked during both compilation and execution of SQL statements. We will // thus test that DROP TABLE statements perform as expected when compilation // and execution are grouped, and when they are performed separately. class DeletionObserver : TransactionObserver { private var notify: ([String: Int]) -> Void private var deletionEvents: [String: Int] = [:] // Notifies table names with the number of deleted rows init(_ notify: @escaping ([String: Int]) -> Void) { self.notify = notify } func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { true } func databaseDidChange(with event: DatabaseEvent) { if case .delete = event.kind { deletionEvents[event.tableName, default: 0] += 1 } } func databaseDidCommit(_ db: Database) { if !deletionEvents.isEmpty { notify(deletionEvents) } deletionEvents = [:] } func databaseDidRollback(_ db: Database) { deletionEvents = [:] } } class UniversalObserver : TransactionObserver { func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { true } func databaseDidChange(with event: DatabaseEvent) { } func databaseDidCommit(_ db: Database) { } func databaseDidRollback(_ db: Database) { } } func testExecuteDelete() throws { let dbQueue = try makeDatabaseQueue() var deletionEvents: [[String: Int]] = [] let observer = DeletionObserver { deletionEvents.append($0) } dbQueue.add(transactionObserver: observer, extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") deletionEvents = [] try db.execute(sql: "DELETE FROM t") XCTAssertEqual(deletionEvents.count, 1) XCTAssertEqual(deletionEvents[0], ["t": 2]) try db.execute(sql: "INSERT INTO t VALUES (NULL)") deletionEvents = [] try db.execute(sql: "DELETE FROM t") XCTAssertEqual(deletionEvents.count, 1) XCTAssertEqual(deletionEvents[0], ["t": 1]) } } func testExecuteDeleteCaseSensitivity() throws { let dbQueue = try makeDatabaseQueue() class DeletionObserver : TransactionObserver { var deletionCount = 0 func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { if case .delete(tableName: "pLaYeR") = eventKind { return true } else { return false } } func databaseDidChange(with event: DatabaseEvent) { if case .delete = event.kind { deletionCount += 1 } } func databaseDidCommit(_ db: Database) { } func databaseDidRollback(_ db: Database) { } } let observer = DeletionObserver() dbQueue.add(transactionObserver: observer, extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in observer.deletionCount = 0 try db.execute(sql: "CREATE TABLE pLaYeR(a)") try db.execute(sql: "INSERT INTO pLaYeR VALUES (NULL)") try db.execute(sql: "INSERT INTO pLaYeR VALUES (NULL)") try db.execute(sql: "DELETE FROM pLaYeR") XCTAssertEqual(observer.deletionCount, 2) observer.deletionCount = 0 try db.execute(sql: "INSERT INTO pLaYeR VALUES (NULL)") try db.execute(sql: "DELETE FROM PLAYER") XCTAssertEqual(observer.deletionCount, 1) observer.deletionCount = 0 try db.execute(sql: "INSERT INTO pLaYeR VALUES (NULL)") try db.execute(sql: "DELETE FROM player") XCTAssertEqual(observer.deletionCount, 1) } } func testExecuteDeleteWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() var deletionEvents: [[String: Int]] = [] let observer = DeletionObserver { deletionEvents.append($0) } dbQueue.add(transactionObserver: observer, extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") let deleteStatement = try db.makeStatement(sql: "DELETE FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") deletionEvents = [] try deleteStatement.execute() XCTAssertEqual(deletionEvents.count, 1) XCTAssertEqual(deletionEvents[0], ["t": 2]) try db.execute(sql: "INSERT INTO t VALUES (NULL)") deletionEvents = [] try deleteStatement.execute() XCTAssertEqual(deletionEvents.count, 1) XCTAssertEqual(deletionEvents[0], ["t": 1]) } } func testExecuteDeleteWithCachedPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() var deletionEvents: [[String: Int]] = [] try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.cachedStatement(sql: "DELETE FROM t").execute() // Start observing after statement has been cached: it does not // prevent the truncate optimization. let observer = DeletionObserver { deletionEvents.append($0) } db.add(transactionObserver: observer, extent: .databaseLifetime) try db.execute(sql: "INSERT INTO t VALUES (NULL)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") deletionEvents = [] // This must be a recompiled statement, so that the truncate // optimization is prevented. try db.cachedStatement(sql: "DELETE FROM t").execute() XCTAssertEqual(deletionEvents.count, 1) XCTAssertEqual(deletionEvents[0], ["t": 2]) try db.execute(sql: "INSERT INTO t VALUES (NULL)") deletionEvents = [] try db.cachedStatement(sql: "DELETE FROM t").execute() XCTAssertEqual(deletionEvents.count, 1) XCTAssertEqual(deletionEvents[0], ["t": 1]) } } func testDropTable() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) try db.execute(sql: "DROP TABLE t") // compile + execute try XCTAssertFalse(db.tableExists("t")) } } func testObservedDropTable() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) try db.execute(sql: "DROP TABLE t") // compile + execute try XCTAssertFalse(db.tableExists("t")) } } func testDropTableWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) let dropStatement = try db.makeStatement(sql: "DROP TABLE t") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.tableExists("t")) } } func testObservedDropTableWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) let dropStatement = try db.makeStatement(sql: "DROP TABLE t") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.tableExists("t")) } } func testDropTemporaryTable() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) try db.execute(sql: "DROP TABLE t") // compile + execute try XCTAssertFalse(db.tableExists("t")) } } func testObservedDropTemporaryTable() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) try db.execute(sql: "DROP TABLE t") // compile + execute try XCTAssertFalse(db.tableExists("t")) } } func testDropTemporaryTableWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) let dropStatement = try db.makeStatement(sql: "DROP TABLE t") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.tableExists("t")) } } func testObservedDropTemporaryTableWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) let dropStatement = try db.makeStatement(sql: "DROP TABLE t") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.tableExists("t")) } } func testDropVirtualTable() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE VIRTUAL TABLE t USING fts3(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) try db.execute(sql: "DROP TABLE t") // compile + execute try XCTAssertFalse(db.tableExists("t")) } } func testObservedDropVirtualTable() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE VIRTUAL TABLE t USING fts3(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) try db.execute(sql: "DROP TABLE t") // compile + execute try XCTAssertFalse(db.tableExists("t")) } } func testDropVirtualTableWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE VIRTUAL TABLE t USING fts3(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) let dropStatement = try db.makeStatement(sql: "DROP TABLE t") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.tableExists("t")) } } func testObservedDropVirtualTableWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE VIRTUAL TABLE t USING fts3(a)") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.tableExists("t")) let dropStatement = try db.makeStatement(sql: "DROP TABLE t") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.tableExists("t")) } } func testDropView() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) try db.execute(sql: "DROP VIEW v") // compile + execute try XCTAssertFalse(db.viewExists("v")) } } func testObservedDropView() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) try db.execute(sql: "DROP VIEW v") // compile + execute try XCTAssertFalse(db.viewExists("v")) } } func testDropViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) let dropStatement = try db.makeStatement(sql: "DROP VIEW v") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.viewExists("v")) } } func testObservedDropViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) let dropStatement = try db.makeStatement(sql: "DROP VIEW v") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.viewExists("v")) } } func testDropTemporaryView() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) try db.execute(sql: "DROP VIEW v") // compile + execute try XCTAssertFalse(db.viewExists("v")) } } func testObservedDropTemporaryView() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) try db.execute(sql: "DROP VIEW v") // compile + execute try XCTAssertFalse(db.viewExists("v")) } } func testDropTemporaryViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) let dropStatement = try db.makeStatement(sql: "DROP VIEW v") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.viewExists("v")) } } func testObservedDropTemporaryViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY VIEW v AS SELECT * FROM t") try db.execute(sql: "INSERT INTO t VALUES (NULL)") try XCTAssertTrue(db.viewExists("v")) let dropStatement = try db.makeStatement(sql: "DROP VIEW v") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.viewExists("v")) } } func testDropIndex() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) try db.execute(sql: "DROP INDEX i") // compile + execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testObservedDropIndex() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) try db.execute(sql: "DROP INDEX i") // compile + execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testDropIndexViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try db.execute(sql: "INSERT INTO t VALUES (1)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) let dropStatement = try db.makeStatement(sql: "DROP INDEX i") // compile... try dropStatement.execute() // ... then execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testObservedDropIndexViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try db.execute(sql: "INSERT INTO t VALUES (1)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) let dropStatement = try db.makeStatement(sql: "DROP INDEX i") // compile... try dropStatement.execute() // ... then execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testDropTemporaryIndex() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) try db.execute(sql: "DROP INDEX i") // compile + execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testObservedDropTemporaryIndex() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) try db.execute(sql: "DROP INDEX i") // compile + execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testDropTemporaryIndexViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try db.execute(sql: "INSERT INTO t VALUES (1)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) let dropStatement = try db.makeStatement(sql: "DROP INDEX i") // compile... try dropStatement.execute() // ... then execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testObservedDropTemporaryIndexViewWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TEMPORARY TABLE t(a)") try db.execute(sql: "CREATE INDEX i ON t(a)") try db.execute(sql: "INSERT INTO t VALUES (1)") try XCTAssertFalse(db.indexes(on: "t").isEmpty) let dropStatement = try db.makeStatement(sql: "DROP INDEX i") // compile... try dropStatement.execute() // ... then execute try XCTAssertTrue(db.indexes(on: "t").isEmpty) } } func testDropTrigger() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) try db.execute(sql: "DROP TRIGGER r") // compile + execute try XCTAssertFalse(db.triggerExists("r")) } } func testObservedDropTrigger() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) try db.execute(sql: "DROP TRIGGER r") // compile + execute try XCTAssertFalse(db.triggerExists("r")) } } func testDropTriggerWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) let dropStatement = try db.makeStatement(sql: "DROP TRIGGER r") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.triggerExists("r")) } } func testObservedDropTriggerWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) let dropStatement = try db.makeStatement(sql: "DROP TRIGGER r") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.triggerExists("r")) } } func testDropTemporaryTrigger() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) try db.execute(sql: "DROP TRIGGER r") // compile + execute try XCTAssertFalse(db.triggerExists("r")) } } func testObservedDropTemporaryTrigger() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) try db.execute(sql: "DROP TRIGGER r") // compile + execute try XCTAssertFalse(db.triggerExists("r")) } } func testDropTemporaryTriggerWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) let dropStatement = try db.makeStatement(sql: "DROP TRIGGER r") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.triggerExists("r")) } } func testObservedDropTemporaryTriggerWithPreparedStatement() throws { let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: UniversalObserver(), extent: .databaseLifetime) try dbQueue.writeWithoutTransaction { db in try db.execute(sql: "CREATE TABLE t(a)") try db.execute(sql: "CREATE TEMPORARY TRIGGER r INSERT ON t BEGIN DELETE FROM t; END") try XCTAssertTrue(db.triggerExists("r")) let dropStatement = try db.makeStatement(sql: "DROP TRIGGER r") // compile... try dropStatement.execute() // ... then execute try XCTAssertFalse(db.triggerExists("r")) } } }