add
This commit is contained in:
-382
@@ -1,382 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user