319 lines
12 KiB
Swift
319 lines
12 KiB
Swift
import XCTest
|
|
import GRDB
|
|
|
|
class DatabaseRegionObservationTests: GRDBTestCase {
|
|
// Test passes if it compiles.
|
|
// See <https://github.com/groue/GRDB.swift/issues/1541>
|
|
func testAnyDatabaseWriter(writer: any DatabaseWriter) throws {
|
|
let observation = DatabaseRegionObservation(tracking: .fullDatabase)
|
|
|
|
_ = observation.start(in: writer, onError: { _ in }, onChange: { _ in })
|
|
|
|
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
|
|
_ = observation.publisher(in: writer)
|
|
}
|
|
}
|
|
|
|
func testDatabaseRegionObservation_FullDatabase() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write {
|
|
try $0.execute(sql: "CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
try $0.execute(sql: "CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
}
|
|
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 3
|
|
|
|
let observation = DatabaseRegionObservation(tracking: .fullDatabase)
|
|
|
|
var count = 0
|
|
let cancellable = observation.start(
|
|
in: dbQueue,
|
|
onError: { XCTFail("Unexpected error: \($0)") },
|
|
onChange: { db in
|
|
count += 1
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (2, 'foo')")
|
|
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (2, 'foo')")
|
|
}
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
|
|
XCTAssertEqual(count, 3)
|
|
}
|
|
}
|
|
|
|
func testDatabaseRegionObservation_ImmediateCancellation() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write {
|
|
try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
}
|
|
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.isInverted = true
|
|
|
|
let observation = DatabaseRegionObservation(tracking: .fullDatabase)
|
|
|
|
let cancellable = observation.start(
|
|
in: dbQueue,
|
|
onError: { XCTFail("Unexpected error: \($0)") },
|
|
onChange: { db in
|
|
notificationExpectation.fulfill()
|
|
})
|
|
cancellable.cancel()
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t (id, name) VALUES (1, 'foo')")
|
|
}
|
|
waitForExpectations(timeout: 0.1, handler: nil)
|
|
}
|
|
}
|
|
|
|
func testDatabaseRegionObservationVariadic() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write {
|
|
try $0.execute(sql: "CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
try $0.execute(sql: "CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
}
|
|
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 3
|
|
|
|
let request1 = SQLRequest<Row>(sql: "SELECT * FROM t1 ORDER BY id")
|
|
let request2 = SQLRequest<Row>(sql: "SELECT * FROM t2 ORDER BY id")
|
|
|
|
let observation = DatabaseRegionObservation(tracking: request1, request2)
|
|
|
|
var count = 0
|
|
let cancellable = observation.start(
|
|
in: dbQueue,
|
|
onError: { XCTFail("Unexpected error: \($0)") },
|
|
onChange: { db in
|
|
count += 1
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (2, 'foo')")
|
|
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (2, 'foo')")
|
|
}
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
|
|
XCTAssertEqual(count, 3)
|
|
}
|
|
}
|
|
|
|
func testDatabaseRegionObservationArray() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write {
|
|
try $0.execute(sql: "CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
try $0.execute(sql: "CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
|
}
|
|
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 3
|
|
|
|
let request1 = SQLRequest<Row>(sql: "SELECT * FROM t1 ORDER BY id")
|
|
let request2 = SQLRequest<Row>(sql: "SELECT * FROM t2 ORDER BY id")
|
|
|
|
let observation = DatabaseRegionObservation(tracking: [request1, request2])
|
|
|
|
var count = 0
|
|
let cancellable = observation.start(
|
|
in: dbQueue,
|
|
onError: { XCTFail("Unexpected error: \($0)") },
|
|
onChange: { db in
|
|
count += 1
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (2, 'foo')")
|
|
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (2, 'foo')")
|
|
}
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
|
|
XCTAssertEqual(count, 3)
|
|
}
|
|
}
|
|
|
|
func testDatabaseRegionDefaultCancellation() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)") }
|
|
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 2
|
|
|
|
let observation = DatabaseRegionObservation(tracking: SQLRequest<Row>(sql: "SELECT * FROM t ORDER BY id"))
|
|
|
|
var count = 0
|
|
do {
|
|
let cancellable = observation.start(
|
|
in: dbQueue,
|
|
onError: { XCTFail("Unexpected error: \($0)") },
|
|
onChange: { db in
|
|
count += 1
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t (id, name) VALUES (1, 'foo')")
|
|
}
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t (id, name) VALUES (2, 'bar')")
|
|
}
|
|
}
|
|
}
|
|
// not notified
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t (id, name) VALUES (3, 'baz')")
|
|
}
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
|
|
XCTAssertEqual(count, 2)
|
|
}
|
|
|
|
func testDatabaseRegionExtentNextTransaction() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)") }
|
|
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 1
|
|
|
|
let observation = DatabaseRegionObservation(tracking: SQLRequest<Row>(sql: "SELECT * FROM t ORDER BY id"))
|
|
|
|
var count = 0
|
|
var cancellable: AnyDatabaseCancellable?
|
|
cancellable = observation.start(
|
|
in: dbQueue,
|
|
onError: { XCTFail("Unexpected error: \($0)") },
|
|
onChange: { db in
|
|
cancellable?.cancel()
|
|
count += 1
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t (id, name) VALUES (1, 'foo')")
|
|
}
|
|
// not notified
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: "INSERT INTO t (id, name) VALUES (2, 'bar')")
|
|
}
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
|
|
XCTAssertEqual(count, 1)
|
|
}
|
|
}
|
|
|
|
func test_DatabaseRegionObservation_is_triggered_by_explicit_change_notification() throws {
|
|
let dbQueue1 = try makeDatabaseQueue(filename: "test.sqlite")
|
|
try dbQueue1.write { db in
|
|
try db.execute(sql: "CREATE TABLE test(a)")
|
|
}
|
|
|
|
let undetectedExpectation = expectation(description: "undetected")
|
|
undetectedExpectation.isInverted = true
|
|
|
|
let detectedExpectation = expectation(description: "detected")
|
|
|
|
let observation = DatabaseRegionObservation(tracking: Table("test"))
|
|
let cancellable = observation.start(
|
|
in: dbQueue1,
|
|
onError: { error in XCTFail("Unexpected error: \(error)") },
|
|
onChange: { _ in
|
|
undetectedExpectation.fulfill()
|
|
detectedExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(cancellable) {
|
|
// Change performed from external connection is not detected...
|
|
let dbQueue2 = try makeDatabaseQueue(filename: "test.sqlite")
|
|
try dbQueue2.write { db in
|
|
try db.execute(sql: "INSERT INTO test (a) VALUES (1)")
|
|
}
|
|
wait(for: [undetectedExpectation], timeout: 2)
|
|
|
|
// ... until we perform an explicit change notification
|
|
try dbQueue1.write { db in
|
|
try db.notifyChanges(in: Table("test"))
|
|
}
|
|
wait(for: [detectedExpectation], timeout: 2)
|
|
}
|
|
}
|
|
|
|
// Regression test for https://github.com/groue/GRDB.swift/issues/514
|
|
// TODO: uncomment and make this test pass.
|
|
// Well, actually, selecting only the rowid has SQLite authorizer advertise
|
|
// that we select the whole table. This creates undesired database
|
|
// observation notifications.
|
|
// func testIssue514() throws {
|
|
// let dbQueue = try makeDatabaseQueue()
|
|
// try dbQueue.write { db in
|
|
// try db.create(table: "gallery") { t in
|
|
// t.primaryKey("id", .integer)
|
|
// t.column("status", .integer)
|
|
// }
|
|
// }
|
|
//
|
|
// struct Gallery: TableRecord { }
|
|
// let observation = DatabaseRegionObservation(tracking: Gallery.select(Column("id")))
|
|
//
|
|
// var notificationCount = 0
|
|
// let cancellable = observation.start(
|
|
// in: dbQueue,
|
|
// onError: { XCTFail("Unexpected error: \($0)") },
|
|
// onChange: { _ in
|
|
// notificationCount += 1
|
|
// })
|
|
//
|
|
// try withExtendedLifetime(cancellable) {
|
|
// try dbQueue.write { db in
|
|
// try db.execute(sql: "INSERT INTO gallery (id, status) VALUES (NULL, 0)")
|
|
// }
|
|
// XCTAssertEqual(notificationCount, 1)
|
|
//
|
|
// try dbQueue.write { db in
|
|
// try db.execute(sql: "UPDATE gallery SET status = 1")
|
|
// }
|
|
// XCTAssertEqual(notificationCount, 1) // status is not observed
|
|
//
|
|
// try dbQueue.write { db in
|
|
// try db.execute(sql: "DELETE FROM gallery")
|
|
// }
|
|
// XCTAssertEqual(notificationCount, 2)
|
|
// }
|
|
// }
|
|
}
|