225 lines
9.0 KiB
Swift
225 lines
9.0 KiB
Swift
import XCTest
|
|
@testable import GRDB
|
|
|
|
class ValueObservationRegionRecordingTests: GRDBTestCase {
|
|
func testRecordingSelectedRegion() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write { db in
|
|
try db.execute(sql: """
|
|
CREATE TABLE team(id INTEGER PRIMARY KEY, name TEXT);
|
|
CREATE TABLE player(id INTEGER PRIMARY KEY, name TEXT);
|
|
""")
|
|
|
|
do {
|
|
var region = DatabaseRegion()
|
|
db.recordingSelection(®ion) { }
|
|
XCTAssertTrue(region.isEmpty)
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion.fullDatabase
|
|
db.recordingSelection(®ion) { }
|
|
XCTAssertTrue(region.isFullDatabase)
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion(table: "player")
|
|
db.recordingSelection(®ion) { }
|
|
XCTAssertEqual(region.description, "player(*)")
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion()
|
|
_ = try db.recordingSelection(®ion) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM team")
|
|
}
|
|
XCTAssertEqual(region.description, "team(id,name)")
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion.fullDatabase
|
|
_ = try db.recordingSelection(®ion) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM team")
|
|
}
|
|
XCTAssertTrue(region.isFullDatabase)
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion(table: "player")
|
|
_ = try db.recordingSelection(®ion) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM team")
|
|
}
|
|
XCTAssertEqual(region.description, "player(*),team(id,name)")
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion()
|
|
_ = try db.recordingSelection(®ion) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT name FROM player")
|
|
}
|
|
XCTAssertEqual(region.description, "player(name)")
|
|
}
|
|
|
|
do {
|
|
// Test for rowID optimization
|
|
struct Player: TableRecord, FetchableRecord, Decodable { }
|
|
var region = DatabaseRegion()
|
|
_ = try db.recordingSelection(®ion) {
|
|
_ = try Player.fetchOne(db, key: 123)
|
|
}
|
|
XCTAssert(region.description.contains("player(id,name)[123]"))
|
|
}
|
|
|
|
do {
|
|
var region = DatabaseRegion()
|
|
_ = try db.recordingSelection(®ion) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM team")
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM player")
|
|
}
|
|
XCTAssertEqual(region.description, "player(id,name),team(id,name)")
|
|
}
|
|
|
|
do {
|
|
var region1 = DatabaseRegion()
|
|
var region2 = DatabaseRegion()
|
|
var region3 = DatabaseRegion()
|
|
var region4 = DatabaseRegion()
|
|
var region5 = DatabaseRegion()
|
|
_ = try db.recordingSelection(®ion1) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM team")
|
|
_ = try db.recordingSelection(®ion2) {
|
|
db.recordingSelection(®ion3) { }
|
|
_ = try Row.fetchAll(db, sql: "SELECT name FROM player")
|
|
db.recordingSelection(®ion4) { }
|
|
}
|
|
_ = try db.recordingSelection(®ion5) {
|
|
_ = try Row.fetchAll(db, sql: "SELECT * FROM player")
|
|
}
|
|
}
|
|
XCTAssertEqual(region1.description, "player(id,name),team(id,name)")
|
|
XCTAssertEqual(region2.description, "player(name)")
|
|
XCTAssertTrue(region3.isEmpty)
|
|
XCTAssertTrue(region4.isEmpty)
|
|
XCTAssertEqual(region5.description, "player(id,name)")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testTupleObservation() throws {
|
|
// Here we just test that user can destructure an observed tuple.
|
|
// I'm completely paranoid about tuple destructuring - I can't wrap my
|
|
// head about the rules that allow or disallow it.
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let observation = ValueObservation.trackingConstantRegion { db -> (Int, String) in
|
|
(0, "")
|
|
}
|
|
_ = observation.start(
|
|
in: dbQueue,
|
|
onError: { _ in },
|
|
onChange: { (int: Int, string: String) in }) // <- destructure
|
|
}
|
|
|
|
func testVaryingRegionTrackingImmediateScheduling() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write {
|
|
try $0.execute(sql: """
|
|
CREATE TABLE source(name TEXT);
|
|
INSERT INTO source VALUES ('a');
|
|
CREATE TABLE a(value INTEGER);
|
|
CREATE TABLE b(value INTEGER);
|
|
""")
|
|
}
|
|
|
|
var results: [Int] = []
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 4
|
|
|
|
var regions: [DatabaseRegion] = []
|
|
let observation = ValueObservation
|
|
.tracking { db -> Int in
|
|
let table = try String.fetchOne(db, sql: "SELECT name FROM source")!
|
|
return try Int.fetchOne(db, sql: "SELECT IFNULL(SUM(value), 0) FROM \(table)")!
|
|
}
|
|
.handleEvents(willTrackRegion: { regions.append($0) })
|
|
|
|
let observer = observation.start(
|
|
in: dbQueue,
|
|
onError: { error in XCTFail("Unexpected error: \(error)") },
|
|
onChange: { count in
|
|
results.append(count)
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(observer) {
|
|
try dbQueue.inDatabase { db in
|
|
try db.execute(sql: "INSERT INTO a VALUES (1)") // 1
|
|
try db.execute(sql: "INSERT INTO b VALUES (1)") // -
|
|
try db.execute(sql: "INSERT INTO b VALUES (1)") // -
|
|
try db.execute(sql: "UPDATE source SET name = 'b'") // 2
|
|
try db.execute(sql: "INSERT INTO a VALUES (1)") // -
|
|
try db.execute(sql: "INSERT INTO b VALUES (1)") // 3
|
|
}
|
|
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
XCTAssertEqual(results, [0, 1, 2, 3])
|
|
|
|
XCTAssertEqual(regions.map(\.description), [
|
|
"a(value),source(name)",
|
|
"b(value),source(name)"])
|
|
}
|
|
}
|
|
|
|
func testVaryingRegionTrackingAsyncScheduling() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.write {
|
|
try $0.execute(sql: """
|
|
CREATE TABLE source(name TEXT);
|
|
INSERT INTO source VALUES ('a');
|
|
CREATE TABLE a(value INTEGER);
|
|
CREATE TABLE b(value INTEGER);
|
|
""")
|
|
}
|
|
|
|
var results: [Int] = []
|
|
let notificationExpectation = expectation(description: "notification")
|
|
notificationExpectation.assertForOverFulfill = true
|
|
notificationExpectation.expectedFulfillmentCount = 4
|
|
|
|
var regions: [DatabaseRegion] = []
|
|
let observation = ValueObservation
|
|
.tracking { db -> Int in
|
|
let table = try String.fetchOne(db, sql: "SELECT name FROM source")!
|
|
return try Int.fetchOne(db, sql: "SELECT IFNULL(SUM(value), 0) FROM \(table)")!
|
|
}
|
|
.handleEvents(willTrackRegion: { regions.append($0) })
|
|
|
|
let observer = observation.start(
|
|
in: dbQueue,
|
|
scheduling: .async(onQueue: .main),
|
|
onError: { error in XCTFail("Unexpected error: \(error)") },
|
|
onChange: { count in
|
|
results.append(count)
|
|
notificationExpectation.fulfill()
|
|
})
|
|
|
|
try withExtendedLifetime(observer) {
|
|
try dbQueue.inDatabase { db in
|
|
try db.execute(sql: "INSERT INTO a VALUES (1)") // 1
|
|
try db.execute(sql: "INSERT INTO b VALUES (1)") // -
|
|
try db.execute(sql: "INSERT INTO b VALUES (1)") // -
|
|
try db.execute(sql: "UPDATE source SET name = 'b'") // 2
|
|
try db.execute(sql: "INSERT INTO a VALUES (1)") // -
|
|
try db.execute(sql: "INSERT INTO b VALUES (1)") // 3
|
|
}
|
|
|
|
waitForExpectations(timeout: 1, handler: nil)
|
|
XCTAssertEqual(results, [0, 1, 2, 3])
|
|
|
|
XCTAssertEqual(regions.map(\.description), [
|
|
"a(value),source(name)",
|
|
"b(value),source(name)"])
|
|
}
|
|
}
|
|
}
|