Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/Tests/GRDBTests/ValueObservationQueryInterfaceRequestTests.swift
T
2025-01-22 14:09:10 +08:00

383 lines
16 KiB
Swift

import XCTest
import GRDB
private struct Parent: TableRecord, FetchableRecord, Decodable, Equatable {
static let children = hasMany(Child.self)
var id: Int64
var name: String
}
private struct Child: TableRecord, FetchableRecord, Decodable, Equatable {
var id: Int64
var parentId: Int64
var name: String
}
private struct ParentInfo: FetchableRecord, Decodable, Equatable {
var parent: Parent
var children: [Child]
}
class ValueObservationQueryInterfaceRequestTests: GRDBTestCase {
private func setup(_ db: Database) throws {
try db.create(table: "parent") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text)
}
try db.create(table: "child") { t in
t.autoIncrementedPrimaryKey("id")
t.belongsTo("parent", onDelete: .cascade)
t.column("name", .text)
}
}
private func performDatabaseModifications(_ db: Database) throws {
try db.inTransaction {
try db.execute(sql: """
INSERT INTO parent (id, name) VALUES (1, 'foo');
INSERT INTO parent (id, name) VALUES (2, 'bar');
""")
return .commit
}
try db.inTransaction {
try db.execute(sql: """
INSERT INTO child (id, parentId, name) VALUES (1, 1, 'fooA');
INSERT INTO child (id, parentId, name) VALUES (2, 1, 'fooB');
INSERT INTO child (id, parentId, name) VALUES (3, 2, 'barA');
""")
return .commit
}
try db.inTransaction {
try db.execute(sql: """
UPDATE child SET name = 'fooA2' WHERE id = 1;
""")
return .commit
}
try db.inTransaction {
try db.execute(sql: """
INSERT INTO parent (id, name) VALUES (3, 'baz');
INSERT INTO child (id, parentId, name) VALUES (4, 3, 'bazA');
""")
return .commit
}
try db.inTransaction {
try db.execute(sql: """
DELETE FROM parent WHERE id = 1;
DELETE FROM child;
""")
return .commit
}
}
func testOneRowWithPrefetchedRows() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.write(setup)
let request = Parent
.including(all: Parent.children.orderByPrimaryKey())
.orderByPrimaryKey()
.asRequest(of: Row.self)
let observation = ValueObservation.trackingConstantRegion(request.fetchOne)
let recorder = observation.record(in: dbQueue)
try dbQueue.writeWithoutTransaction(performDatabaseModifications)
let results = try wait(for: recorder.next(6), timeout: 5)
XCTAssertNil(results[0])
XCTAssertEqual(results[1]!.unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[1]!.prefetchedRows["children"], [])
XCTAssertEqual(results[2]!.unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[2]!.prefetchedRows["children"], [
["id": 1, "parentId": 1, "name": "fooA", "grdb_parentId": 1],
["id": 2, "parentId": 1, "name": "fooB", "grdb_parentId": 1]])
XCTAssertEqual(results[3]!.unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[3]!.prefetchedRows["children"], [
["id": 1, "parentId": 1, "name": "fooA2", "grdb_parentId": 1],
["id": 2, "parentId": 1, "name": "fooB", "grdb_parentId": 1]])
XCTAssertEqual(results[4]!.unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[4]!.prefetchedRows["children"], [
["id": 1, "parentId": 1, "name": "fooA2", "grdb_parentId": 1],
["id": 2, "parentId": 1, "name": "fooB", "grdb_parentId": 1]])
XCTAssertEqual(results[5]!.unscoped, ["id": 2, "name": "bar"])
XCTAssertEqual(results[5]!.prefetchedRows["children"], [])
}
func testAllRowsWithPrefetchedRows() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.write(setup)
let request = Parent
.including(all: Parent.children.orderByPrimaryKey())
.orderByPrimaryKey()
.asRequest(of: Row.self)
let observation = ValueObservation.trackingConstantRegion(request.fetchAll)
let recorder = observation.record(in: dbQueue)
try dbQueue.writeWithoutTransaction(performDatabaseModifications)
let results = try wait(for: recorder.next(6), timeout: 5)
XCTAssertEqual(results[0].count, 0)
XCTAssertEqual(results[1].count, 2)
XCTAssertEqual(results[1][0].unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[1][0].prefetchedRows["children"], [])
XCTAssertEqual(results[1][1].unscoped, ["id": 2, "name": "bar"])
XCTAssertEqual(results[1][1].prefetchedRows["children"], [])
XCTAssertEqual(results[2].count, 2)
XCTAssertEqual(results[2][0].unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[2][0].prefetchedRows["children"], [
["id": 1, "parentId": 1, "name": "fooA", "grdb_parentId": 1],
["id": 2, "parentId": 1, "name": "fooB", "grdb_parentId": 1]])
XCTAssertEqual(results[2][1].unscoped, ["id": 2, "name": "bar"])
XCTAssertEqual(results[2][1].prefetchedRows["children"], [
["id": 3, "parentId": 2, "name": "barA", "grdb_parentId": 2]])
XCTAssertEqual(results[3].count, 2)
XCTAssertEqual(results[3][0].unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[3][0].prefetchedRows["children"], [
["id": 1, "parentId": 1, "name": "fooA2", "grdb_parentId": 1],
["id": 2, "parentId": 1, "name": "fooB", "grdb_parentId": 1]])
XCTAssertEqual(results[3][1].unscoped, ["id": 2, "name": "bar"])
XCTAssertEqual(results[3][1].prefetchedRows["children"], [
["id": 3, "parentId": 2, "name": "barA", "grdb_parentId": 2]])
XCTAssertEqual(results[4].count, 3)
XCTAssertEqual(results[4][0].unscoped, ["id": 1, "name": "foo"])
XCTAssertEqual(results[4][0].prefetchedRows["children"], [
["id": 1, "parentId": 1, "name": "fooA2", "grdb_parentId": 1],
["id": 2, "parentId": 1, "name": "fooB", "grdb_parentId": 1]])
XCTAssertEqual(results[4][1].unscoped, ["id": 2, "name": "bar"])
XCTAssertEqual(results[4][1].prefetchedRows["children"], [
["id": 3, "parentId": 2, "name": "barA", "grdb_parentId": 2]])
XCTAssertEqual(results[4][2].unscoped, ["id": 3, "name": "baz"])
XCTAssertEqual(results[4][2].prefetchedRows["children"], [
["id": 4, "parentId": 3, "name": "bazA", "grdb_parentId": 3]])
XCTAssertEqual(results[5].count, 2)
XCTAssertEqual(results[5][0].unscoped, ["id": 2, "name": "bar"])
XCTAssertEqual(results[5][0].prefetchedRows["children"], [])
XCTAssertEqual(results[5][1].unscoped, ["id": 3, "name": "baz"])
XCTAssertEqual(results[5][1].prefetchedRows["children"], [])
}
func testOneRecordWithPrefetchedRows() throws {
let request = Parent
.including(all: Parent.children.orderByPrimaryKey())
.orderByPrimaryKey()
.asRequest(of: ParentInfo.self)
try assertValueObservation(
ValueObservation.trackingConstantRegion(request.fetchOne),
records: [
nil,
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: []),
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: []),
],
setup: setup,
recordedUpdates: performDatabaseModifications)
// The fundamental technique for removing duplicates of non-Equatable types
try assertValueObservation(
ValueObservation
.trackingConstantRegion { db in try Row.fetchOne(db, request) }
.removeDuplicates()
.map { row in try row.map(ParentInfo.init(row:)) },
records: [
nil,
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: []),
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: []),
],
setup: setup,
recordedUpdates: performDatabaseModifications)
}
func testAllRecordsWithPrefetchedRows() throws {
let request = Parent
.including(all: Parent.children.orderByPrimaryKey())
.orderByPrimaryKey()
.asRequest(of: ParentInfo.self)
try assertValueObservation(
ValueObservation.trackingConstantRegion(request.fetchAll),
records: [
[],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: []),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: []),
],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: [
Child(id: 3, parentId: 2, name: "barA"),
]),
],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: [
Child(id: 3, parentId: 2, name: "barA"),
]),
],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: [
Child(id: 3, parentId: 2, name: "barA"),
]),
ParentInfo(
parent: Parent(id: 3, name: "baz"),
children: [
Child(id: 4, parentId: 3, name: "bazA"),
]),
],
[
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: []),
ParentInfo(
parent: Parent(id: 3, name: "baz"),
children: []),
],
],
setup: setup,
recordedUpdates: performDatabaseModifications)
// The fundamental technique for removing duplicates of non-Equatable types
try assertValueObservation(
ValueObservation
.trackingConstantRegion { db in try Row.fetchAll(db, request) }
.removeDuplicates()
.map { rows in try rows.map(ParentInfo.init(row:)) },
records: [
[],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: []),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: []),
],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: [
Child(id: 3, parentId: 2, name: "barA"),
]),
],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: [
Child(id: 3, parentId: 2, name: "barA"),
]),
],
[
ParentInfo(
parent: Parent(id: 1, name: "foo"),
children: [
Child(id: 1, parentId: 1, name: "fooA2"),
Child(id: 2, parentId: 1, name: "fooB"),
]),
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: [
Child(id: 3, parentId: 2, name: "barA"),
]),
ParentInfo(
parent: Parent(id: 3, name: "baz"),
children: [
Child(id: 4, parentId: 3, name: "bazA"),
]),
],
[
ParentInfo(
parent: Parent(id: 2, name: "bar"),
children: []),
ParentInfo(
parent: Parent(id: 3, name: "baz"),
children: []),
],
],
setup: setup,
recordedUpdates: performDatabaseModifications)
}
}