Files

1191 lines
64 KiB
Swift

import XCTest
import GRDB
private struct A: TableRecord { }
private struct B: TableRecord { }
private struct C: TableRecord { }
private struct D: TableRecord { }
class AssociationPrefetchingRowTests: GRDBTestCase {
override func setup(_ dbWriter: some DatabaseWriter) throws {
try dbWriter.write { db in
try db.create(table: "a") { t in
t.autoIncrementedPrimaryKey("cola1")
t.column("cola2", .text)
}
try db.create(table: "b") { t in
t.autoIncrementedPrimaryKey("colb1")
t.column("colb2", .integer).references("a")
t.column("colb3", .text)
}
try db.create(table: "c") { t in
t.autoIncrementedPrimaryKey("colc1")
t.column("colc2", .integer).references("a")
}
try db.create(table: "d") { t in
t.autoIncrementedPrimaryKey("cold1")
t.column("cold2", .integer).references("c")
t.column("cold3", .text)
}
try db.execute(
sql: """
INSERT INTO a (cola1, cola2) VALUES (?, ?);
INSERT INTO a (cola1, cola2) VALUES (?, ?);
INSERT INTO a (cola1, cola2) VALUES (?, ?);
INSERT INTO b (colb1, colb2, colb3) VALUES (?, ?, ?);
INSERT INTO b (colb1, colb2, colb3) VALUES (?, ?, ?);
INSERT INTO b (colb1, colb2, colb3) VALUES (?, ?, ?);
INSERT INTO b (colb1, colb2, colb3) VALUES (?, ?, ?);
INSERT INTO c (colc1, colc2) VALUES (?, ?);
INSERT INTO c (colc1, colc2) VALUES (?, ?);
INSERT INTO c (colc1, colc2) VALUES (?, ?);
INSERT INTO d (cold1, cold2, cold3) VALUES (?, ?, ?);
INSERT INTO d (cold1, cold2, cold3) VALUES (?, ?, ?);
INSERT INTO d (cold1, cold2, cold3) VALUES (?, ?, ?);
INSERT INTO d (cold1, cold2, cold3) VALUES (?, ?, ?);
INSERT INTO d (cold1, cold2, cold3) VALUES (?, ?, ?);
""",
arguments: [
1, "a1",
2, "a2",
3, "a3",
4, 1, "b1",
5, 1, "b2",
6, 2, "b3",
14, nil, "b4",
7, 1,
8, 2,
9, 2,
10, 7, "d1",
11, 8, "d2",
12, 8, "d3",
13, 9, "d4",
14, nil, "d5",
])
}
}
func testIncludingAllHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
// Plain request
do {
let request = A
.including(all: A
.hasMany(B.self)
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 3)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ bs: 2 rows
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[0].prefetchedRows["bs"]!.count, 2)
XCTAssertEqual(rows[0].prefetchedRows["bs"]![0], ["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colb2": 1])
XCTAssertEqual(rows[0].prefetchedRows["bs"]![1], ["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colb2": 1])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[1].prefetchedRows["bs"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["bs"]![0], ["colb1": 6, "colb2": 2, "colb3": "b3", "grdb_colb2": 2])
XCTAssertEqual(rows[2].unscoped, ["cola1": 3, "cola2": "a3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[2].prefetchedRows["bs"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["bs"])
XCTAssertEqual(row.prefetchedRows["bs"]!.count, 2)
XCTAssertEqual(row.prefetchedRows["bs"]![0], ["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colb2": 1])
XCTAssertEqual(row.prefetchedRows["bs"]![1], ["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colb2": 1])
}
}
// Request with avoided prefetch
do {
let request = A
.none()
.including(all: A
.hasMany(B.self)
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 0)
}
}
// Request with filters
do {
let request = A
.filter(Column("cola1") != 3)
.including(all: A
.hasMany(B.self)
.filter(Column("colb1") == 4)
.orderByPrimaryKey()
.forKey("bs1"))
.including(all: A
.hasMany(B.self)
.filter(Column("colb1") != 4)
.orderByPrimaryKey()
.forKey("bs2"))
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ bs1: 1 row
+ bs2: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["bs1", "bs2"])
XCTAssertEqual(rows[0].prefetchedRows["bs1"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["bs1"]![0], ["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colb2": 1])
XCTAssertEqual(rows[0].prefetchedRows["bs2"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["bs2"]![0], ["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colb2": 1])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["bs1", "bs2"])
XCTAssertEqual(rows[1].prefetchedRows["bs1"]!.count, 0)
XCTAssertEqual(rows[1].prefetchedRows["bs2"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["bs2"]![0], ["colb1": 6, "colb2": 2, "colb3": "b3", "grdb_colb2": 2])
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["bs1", "bs2"])
XCTAssertEqual(row.prefetchedRows["bs1"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["bs1"]![0], ["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colb2": 1])
XCTAssertEqual(row.prefetchedRows["bs2"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["bs2"]![0], ["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colb2": 1])
}
}
}
}
func testIncludingAllHasManyIncludingAllHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
// Plain request
do {
let request = A
.including(all: A
.hasMany(C.self)
.including(all: C
.hasMany(D.self)
.orderByPrimaryKey())
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 3)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ cs: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].prefetchedRows.keys, ["ds"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].prefetchedRows["ds"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].prefetchedRows["ds"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_cold2": 7])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]!.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].prefetchedRows.keys, ["ds"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].prefetchedRows["ds"]!.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].prefetchedRows["ds"]![0], ["cold1": 11, "cold2": 8, "cold3": "d2", "grdb_cold2": 8])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].prefetchedRows["ds"]![1], ["cold1": 12, "cold2": 8, "cold3": "d3", "grdb_cold2": 8])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].unscoped, ["colc1": 9, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].prefetchedRows.keys, ["ds"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].prefetchedRows["ds"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].prefetchedRows["ds"]![0], ["cold1": 13, "cold2": 9, "cold3": "d4", "grdb_cold2": 9])
XCTAssertEqual(rows[2].unscoped, ["cola1": 3, "cola2": "a3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].prefetchedRows["cs"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(row.prefetchedRows["cs"]![0].prefetchedRows.keys, ["ds"])
XCTAssertEqual(row.prefetchedRows["cs"]![0].prefetchedRows["ds"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs"]![0].prefetchedRows["ds"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_cold2": 7])
}
}
// Request with avoided prefetch
do {
let request = A
.including(all: A
.hasMany(C.self)
.none()
.including(all: C
.hasMany(D.self)
.orderByPrimaryKey())
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 3)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ cs: 0 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]!.count, 0)
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]!.count, 0)
XCTAssertEqual(rows[2].unscoped, ["cola1": 3, "cola2": "a3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].prefetchedRows["cs"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.prefetchedRows["cs"]!.count, 0)
}
}
// Request with filters
do {
let request = A
.filter(Column("cola1") != 3)
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") > 7)
.including(all: C
.hasMany(D.self)
.filter(Column("cold1") == 11)
.orderByPrimaryKey()
.forKey("ds1"))
.including(all: C
.hasMany(D.self)
.filter(Column("cold1") != 11)
.orderByPrimaryKey()
.forKey("ds2"))
.orderByPrimaryKey()
.forKey("cs1"))
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") < 9)
.including(all: C
.hasMany(D.self)
.filter(Column("cold1") == 11)
.orderByPrimaryKey()
.forKey("ds1"))
.including(all: C
.hasMany(D.self)
.filter(Column("cold1") != 11)
.orderByPrimaryKey()
.forKey("ds2"))
.orderByPrimaryKey()
.forKey("cs2"))
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ cs1: 0 row
+ cs2: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[0].prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].prefetchedRows.keys, ["ds1", "ds2"])
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].prefetchedRows["ds1"]!.count, 0)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].prefetchedRows["ds2"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_cold2": 7])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]!.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].prefetchedRows.keys, ["ds1", "ds2"])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].prefetchedRows["ds1"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].prefetchedRows["ds1"]![0], ["cold1": 11, "cold2": 8, "cold3": "d2", "grdb_cold2": 8])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].prefetchedRows["ds2"]![0], ["cold1": 12, "cold2": 8, "cold3": "d3", "grdb_cold2": 8])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].unscoped, ["colc1": 9, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].prefetchedRows.keys, ["ds1", "ds2"])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].prefetchedRows["ds1"]!.count, 0)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].prefetchedRows["ds2"]![0], ["cold1": 13, "cold2": 9, "cold3": "d4", "grdb_cold2": 9])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].prefetchedRows.keys, ["ds1", "ds2"])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].prefetchedRows["ds1"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].prefetchedRows["ds1"]![0], ["cold1": 11, "cold2": 8, "cold3": "d2", "grdb_cold2": 8])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].prefetchedRows["ds2"]![0], ["cold1": 12, "cold2": 8, "cold3": "d3", "grdb_cold2": 8])
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(row.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(row.prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs2"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(row.prefetchedRows["cs2"]![0].prefetchedRows.keys, ["ds1", "ds2"])
XCTAssertEqual(row.prefetchedRows["cs2"]![0].prefetchedRows["ds1"]!.count, 0)
XCTAssertEqual(row.prefetchedRows["cs2"]![0].prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs2"]![0].prefetchedRows["ds2"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_cold2": 7])
}
}
}
}
func testIncludingAllHasManyIncludingRequiredOrOptionalHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
// Plain request
do {
let request = A
.including(all: A
.hasMany(C.self)
.including(required: C
.hasMany(D.self)
.orderByPrimaryKey())
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 3)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ cs: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].scopes.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0].scopes["d"], ["cold1": 10, "cold2": 7, "cold3": "d1"])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]!.count, 3)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].scopes.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0].scopes["d"], ["cold1": 11, "cold2": 8, "cold3": "d2"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].scopes.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![1].scopes["d"], ["cold1": 12, "cold2": 8, "cold3": "d3"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![2].unscoped, ["colc1": 9, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs"]![2].scopes.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![2].scopes["d"], ["cold1": 13, "cold2": 9, "cold3": "d4"])
XCTAssertEqual(rows[2].unscoped, ["cola1": 3, "cola2": "a3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].prefetchedRows["cs"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(row.prefetchedRows["cs"]![0].scopes.count, 1)
XCTAssertEqual(row.prefetchedRows["cs"]![0].scopes["d"], ["cold1": 10, "cold2": 7, "cold3": "d1"])
}
}
// Request with avoided prefetch
do {
let request = A
.including(all: A
.hasMany(C.self)
.none()
.including(required: C
.hasMany(D.self)
.orderByPrimaryKey())
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 3)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ cs: 0 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]!.count, 0)
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]!.count, 0)
XCTAssertEqual(rows[2].unscoped, ["cola1": 3, "cola2": "a3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].prefetchedRows["cs"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.prefetchedRows["cs"]!.count, 0)
}
}
// Request with filters
do {
let request = A
.filter(Column("cola1") != 3)
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") > 7)
.including(optional: C
.hasMany(D.self)
.filter(Column("cold1") == 11)
.orderByPrimaryKey()
.forKey("ds1"))
.including(required: C
.hasMany(D.self)
.filter(Column("cold1") != 11)
.orderByPrimaryKey()
.forKey("ds2"))
.orderByPrimaryKey()
.forKey("cs1"))
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") < 9)
.including(optional: C
.hasMany(D.self)
.filter(Column("cold1") == 11)
.orderByPrimaryKey()
.forKey("ds1"))
.including(required: C
.hasMany(D.self)
.filter(Column("cold1") != 11)
.orderByPrimaryKey()
.forKey("ds2"))
.orderByPrimaryKey()
.forKey("cs2"))
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ cs1: 0 row
+ cs2: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[0].prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].scopes.count, 2)
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].scopes["d1"], ["cold1": nil, "cold2": nil, "cold3": nil])
XCTAssertEqual(rows[0].prefetchedRows["cs2"]![0].scopes["d2"], ["cold1": 10, "cold2": 7, "cold3": "d1"])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]!.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].scopes.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].scopes["d1"], ["cold1": 11, "cold2": 8, "cold3": "d2"])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![0].scopes["d2"], ["cold1": 12, "cold2": 8, "cold3": "d3"])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].unscoped, ["colc1": 9, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].scopes.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].scopes["d1"], ["cold1": nil, "cold2": nil, "cold3": nil])
XCTAssertEqual(rows[1].prefetchedRows["cs1"]![1].scopes["d2"], ["cold1": 13, "cold2": 9, "cold3": "d4"])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].unscoped, ["colc1": 8, "colc2": 2, "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].scopes.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].scopes["d1"], ["cold1": 11, "cold2": 8, "cold3": "d2"])
XCTAssertEqual(rows[1].prefetchedRows["cs2"]![0].scopes["d2"], ["cold1": 12, "cold2": 8, "cold3": "d3"])
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(row.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(row.prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs2"]![0].unscoped, ["colc1": 7, "colc2": 1, "grdb_colc2": 1])
XCTAssertEqual(row.prefetchedRows["cs2"]![0].scopes.count, 2)
XCTAssertEqual(row.prefetchedRows["cs2"]![0].scopes["d1"], ["cold1": nil, "cold2": nil, "cold3": nil])
XCTAssertEqual(row.prefetchedRows["cs2"]![0].scopes["d2"], ["cold1": 10, "cold2": 7, "cold3": "d1"])
}
}
}
}
func testIncludingAllHasManyThroughHasManyUsingHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
// Plain request
do {
let request = A
.including(all: A
.hasMany(D.self, through: A.hasMany(C.self), using: C.hasMany(D.self))
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 3)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ ds: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["ds"])
XCTAssertEqual(rows[0].prefetchedRows["ds"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["ds"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_colc2": 1])
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["ds"])
XCTAssertEqual(rows[1].prefetchedRows["ds"]!.count, 3)
XCTAssertEqual(rows[1].prefetchedRows["ds"]![0], ["cold1": 11, "cold2": 8, "cold3": "d2", "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["ds"]![1], ["cold1": 12, "cold2": 8, "cold3": "d3", "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["ds"]![2], ["cold1": 13, "cold2": 9, "cold3": "d4", "grdb_colc2": 2])
XCTAssertEqual(rows[2].unscoped, ["cola1": 3, "cola2": "a3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["ds"])
XCTAssertEqual(rows[2].prefetchedRows["ds"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["ds"])
XCTAssertEqual(row.prefetchedRows["ds"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["ds"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_colc2": 1])
}
}
// Request with filters
do {
let request = A
.filter(Column("cola1") != 3)
.including(all: A
.hasMany(D.self, through: A.hasMany(C.self).filter(Column("colc1") == 8).forKey("cs1"), using: C.hasMany(D.self))
.orderByPrimaryKey()
.forKey("ds1"))
.including(all: A
.hasMany(D.self, through: A.hasMany(C.self).forKey("cs2"), using: C.hasMany(D.self))
.filter(Column("cold1") != 11)
.orderByPrimaryKey()
.forKey("ds2"))
.including(all: A
.hasMany(D.self, through: A.hasMany(C.self).forKey("cs2"), using: C.hasMany(D.self))
.filter(Column("cold1") == 11)
.orderByPrimaryKey()
.forKey("ds3"))
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 2)
XCTAssertEqual(rows[0].description, "[cola1:1 cola2:\"a1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cola1:1 cola2:"a1"]
+ ds1: 0 row
+ ds2: 1 row
+ ds3: 0 row
""")
XCTAssertEqual(rows[0].unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["ds1", "ds2", "ds3"])
XCTAssertEqual(rows[0].prefetchedRows["ds1"]!.count, 0)
XCTAssertEqual(rows[0].prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["ds2"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_colc2": 1])
XCTAssertEqual(rows[0].prefetchedRows["ds3"]!.count, 0)
XCTAssertEqual(rows[1].unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["ds1", "ds2", "ds3"])
XCTAssertEqual(rows[1].prefetchedRows["ds1"]!.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["ds1"]![0], ["cold1": 11, "cold2": 8, "cold3": "d2", "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["ds1"]![1], ["cold1": 12, "cold2": 8, "cold3": "d3", "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["ds2"]!.count, 2)
XCTAssertEqual(rows[1].prefetchedRows["ds2"]![0], ["cold1": 12, "cold2": 8, "cold3": "d3", "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["ds2"]![1], ["cold1": 13, "cold2": 9, "cold3": "d4", "grdb_colc2": 2])
XCTAssertEqual(rows[1].prefetchedRows["ds3"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["ds3"]![0], ["cold1": 11, "cold2": 8, "cold3": "d2", "grdb_colc2": 2])
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.prefetchedRows.keys, ["ds1", "ds2", "ds3"])
XCTAssertEqual(row.prefetchedRows["ds1"]!.count, 0)
XCTAssertEqual(row.prefetchedRows["ds2"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["ds2"]![0], ["cold1": 10, "cold2": 7, "cold3": "d1", "grdb_colc2": 1])
XCTAssertEqual(row.prefetchedRows["ds3"]!.count, 0)
}
}
}
}
func testIncludingAllHasManyThroughBelongsToUsingHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
// Plain request
do {
let request = B
.including(all: B
.hasMany(C.self, through: B.belongsTo(A.self), using: A.hasMany(C.self))
.orderByPrimaryKey())
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 4)
XCTAssertEqual(rows[0].description, "[colb1:4 colb2:1 colb3:\"b1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [colb1:4 colb2:1 colb3:"b1"]
+ cs: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["colb1": 4, "colb2": 1, "colb3": "b1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssert(rows[0].scopes.isEmpty)
XCTAssertEqual(rows[1].unscoped, ["colb1": 5, "colb2": 1, "colb3": "b2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssert(rows[1].scopes.isEmpty)
XCTAssertEqual(rows[2].unscoped, ["colb1": 6, "colb2": 2, "colb3": "b3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].prefetchedRows["cs"]!.count, 2)
XCTAssertEqual(rows[2].prefetchedRows["cs"]![0], ["colc1": 8, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[2].prefetchedRows["cs"]![1], ["colc1": 9, "colc2": 2, "grdb_cola1": 2])
XCTAssert(rows[2].scopes.isEmpty)
XCTAssertEqual(rows[3].unscoped, ["colb1": 14, "colb2": nil, "colb3": "b4"])
XCTAssertEqual(rows[3].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[3].prefetchedRows["cs"]!.count, 0)
XCTAssert(rows[3].scopes.isEmpty)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["colb1": 4, "colb2": 1, "colb3": "b1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssert(row.scopes.isEmpty)
}
}
}
}
func testIncludingOptionalBelongsToIncludingAllHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.write { db in
// Plain request
do {
let request = B
.including(optional: B
.belongsTo(A.self)
.including(all: A
.hasMany(C.self)
.orderByPrimaryKey())
)
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 4)
XCTAssertEqual(rows[0].description, "[colb1:4 colb2:1 colb3:\"b1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [colb1:4 colb2:1 colb3:"b1"]
unadapted: [colb1:4 colb2:1 colb3:"b1" cola1:1 cola2:"a1"]
- a: [cola1:1 cola2:"a1"]
+ cs: 1 row
+ cs: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["colb1": 4, "colb2": 1, "colb3": "b1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[0].prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(rows[0].scopes.count, 1)
XCTAssertEqual(rows[0].scopes["a"]!.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].scopes["a"]!.prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[0].scopes["a"]!.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[0].scopes["a"]!.prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(rows[1].unscoped, ["colb1": 5, "colb2": 1, "colb3": "b2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(rows[1].scopes.count, 1)
XCTAssertEqual(rows[1].scopes["a"]!.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[1].scopes["a"]!.prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[1].scopes["a"]!.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(rows[1].scopes["a"]!.prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(rows[2].unscoped, ["colb1": 6, "colb2": 2, "colb3": "b3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].prefetchedRows["cs"]!.count, 2)
XCTAssertEqual(rows[2].prefetchedRows["cs"]![0], ["colc1": 8, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[2].prefetchedRows["cs"]![1], ["colc1": 9, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[2].scopes.count, 1)
XCTAssertEqual(rows[2].scopes["a"]!.unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[2].scopes["a"]!.prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[2].scopes["a"]!.prefetchedRows["cs"]!.count, 2)
XCTAssertEqual(rows[2].scopes["a"]!.prefetchedRows["cs"]![0], ["colc1": 8, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[2].scopes["a"]!.prefetchedRows["cs"]![1], ["colc1": 9, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[3].unscoped, ["colb1": 14, "colb2": nil, "colb3": "b4"])
XCTAssertEqual(rows[3].prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[3].prefetchedRows["cs"]!.count, 0)
XCTAssertEqual(rows[3].scopes.count, 1)
XCTAssertEqual(rows[3].scopes["a"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(rows[3].scopes["a"]!.prefetchedRows.keys, ["cs"])
XCTAssertEqual(rows[3].scopes["a"]!.prefetchedRows["cs"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["colb1": 4, "colb2": 1, "colb3": "b1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(row.prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(row.scopes.count, 1)
XCTAssertEqual(row.scopes["a"]!.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.scopes["a"]!.prefetchedRows.keys, ["cs"])
XCTAssertEqual(row.scopes["a"]!.prefetchedRows["cs"]!.count, 1)
XCTAssertEqual(row.scopes["a"]!.prefetchedRows["cs"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
}
}
// Request with filters
do {
let request = B
.including(optional: B
.belongsTo(A.self)
.filter(Column("cola2") == "a1")
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") == 9)
.orderByPrimaryKey()
.forKey("cs1"))
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") != 9)
.orderByPrimaryKey()
.forKey("cs2"))
.forKey("a1"))
.including(optional: B
.belongsTo(A.self)
.filter(Column("cola2") == "a2")
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") == 9)
.orderByPrimaryKey()
.forKey("cs1"))
.including(all: A
.hasMany(C.self)
.filter(Column("colc1") != 9)
.orderByPrimaryKey()
.forKey("cs2"))
.forKey("a2"))
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 4)
XCTAssertEqual(rows[0].description, "[colb1:4 colb2:1 colb3:\"b1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [colb1:4 colb2:1 colb3:"b1"]
unadapted: [colb1:4 colb2:1 colb3:"b1" cola1:1 cola2:"a1" cola1:NULL cola2:NULL]
- a1: [cola1:1 cola2:"a1"]
+ cs1: 0 row
+ cs2: 1 row
- a2: [cola1:NULL cola2:NULL]
+ cs1: 0 row
+ cs2: 0 row
+ cs1: 0 row
+ cs2: 1 row
""")
XCTAssertEqual(rows[0].unscoped, ["colb1": 4, "colb2": 1, "colb3": "b1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[0].scopes.count, 2)
XCTAssertEqual(rows[0].scopes["a1"]!.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[0].scopes["a1"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[0].scopes["a1"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[0].scopes["a1"]!.prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[0].scopes["a1"]!.prefetchedRows["cs2"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(rows[0].scopes["a2"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(rows[0].scopes["a2"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[0].scopes["a2"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[0].scopes["a2"]!.prefetchedRows["cs2"]!.count, 0)
XCTAssertEqual(rows[1].unscoped, ["colb1": 5, "colb2": 1, "colb3": "b2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[1].scopes.count, 2)
XCTAssertEqual(rows[1].scopes["a1"]!.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(rows[1].scopes["a1"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[1].scopes["a1"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[1].scopes["a1"]!.prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[1].scopes["a1"]!.prefetchedRows["cs2"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(rows[1].scopes["a2"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(rows[1].scopes["a2"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[1].scopes["a2"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[1].scopes["a2"]!.prefetchedRows["cs2"]!.count, 0)
XCTAssertEqual(rows[2].unscoped, ["colb1": 6, "colb2": 2, "colb3": "b3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[2].scopes.count, 2)
XCTAssertEqual(rows[2].scopes["a1"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(rows[2].scopes["a1"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[2].scopes["a1"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[2].scopes["a1"]!.prefetchedRows["cs2"]!.count, 0)
XCTAssertEqual(rows[2].scopes["a2"]!.unscoped, ["cola1": 2, "cola2": "a2"])
XCTAssertEqual(rows[2].scopes["a2"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[2].scopes["a2"]!.prefetchedRows["cs1"]!.count, 1)
XCTAssertEqual(rows[2].scopes["a2"]!.prefetchedRows["cs1"]![0], ["colc1": 9, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[2].scopes["a2"]!.prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(rows[2].scopes["a2"]!.prefetchedRows["cs2"]![0], ["colc1": 8, "colc2": 2, "grdb_cola1": 2])
XCTAssertEqual(rows[3].unscoped, ["colb1": 14, "colb2": nil, "colb3": "b4"])
XCTAssertEqual(rows[3].prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[3].scopes.count, 2)
XCTAssertEqual(rows[3].scopes["a1"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(rows[3].scopes["a1"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[3].scopes["a1"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[3].scopes["a1"]!.prefetchedRows["cs2"]!.count, 0)
XCTAssertEqual(rows[3].scopes["a2"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(rows[3].scopes["a2"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(rows[3].scopes["a2"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(rows[3].scopes["a2"]!.prefetchedRows["cs2"]!.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["colb1": 4, "colb2": 1, "colb3": "b1"])
XCTAssertEqual(row.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(row.scopes.count, 2)
XCTAssertEqual(row.scopes["a1"]!.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row.scopes["a1"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(row.scopes["a1"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(row.scopes["a1"]!.prefetchedRows["cs2"]!.count, 1)
XCTAssertEqual(row.scopes["a1"]!.prefetchedRows["cs2"]![0], ["colc1": 7, "colc2": 1, "grdb_cola1": 1])
XCTAssertEqual(row.scopes["a2"]!.unscoped, ["cola1": nil, "cola2": nil])
XCTAssertEqual(row.scopes["a2"]!.prefetchedRows.keys, ["cs1", "cs2"])
XCTAssertEqual(row.scopes["a2"]!.prefetchedRows["cs1"]!.count, 0)
XCTAssertEqual(row.scopes["a2"]!.prefetchedRows["cs2"]!.count, 0)
}
}
}
}
func testJoiningOptionalHasOneThroughIncludingAllHasMany() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.write { db in
// Plain request
do {
let request = D
.joining(optional: D
.hasOne(A.self, through: D.belongsTo(C.self), using: C.belongsTo(A.self))
.including(all: A
.hasMany(B.self)
.orderByPrimaryKey()))
.orderByPrimaryKey()
// Row.fetchAll
do {
let rows = try Row.fetchAll(db, request)
XCTAssertEqual(rows.count, 5)
XCTAssertEqual(rows[0].description, "[cold1:10 cold2:7 cold3:\"d1\"]")
XCTAssertEqual(rows[0].debugDescription, """
▿ [cold1:10 cold2:7 cold3:"d1"]
+ bs: 2 rows
""")
XCTAssertEqual(rows[0].unscoped, ["cold1": 10, "cold2": 7, "cold3": "d1"])
XCTAssertEqual(rows[0].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[0].prefetchedRows["bs"]!.count, 2)
XCTAssertEqual(rows[0].prefetchedRows["bs"]![0], ["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colc1": 7])
XCTAssertEqual(rows[0].prefetchedRows["bs"]![1], ["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colc1": 7])
XCTAssertEqual(rows[0].scopes.count, 0)
XCTAssertEqual(rows[1].unscoped, ["cold1": 11, "cold2": 8, "cold3": "d2"])
XCTAssertEqual(rows[1].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[1].prefetchedRows["bs"]!.count, 1)
XCTAssertEqual(rows[1].prefetchedRows["bs"]![0], ["colb1": 6, "colb2": 2, "colb3": "b3", "grdb_colc1": 8])
XCTAssertEqual(rows[1].scopes.count, 0)
XCTAssertEqual(rows[2].unscoped, ["cold1": 12, "cold2": 8, "cold3": "d3"])
XCTAssertEqual(rows[2].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[2].prefetchedRows["bs"]!.count, 1)
XCTAssertEqual(rows[2].prefetchedRows["bs"]![0], ["colb1": 6, "colb2": 2, "colb3": "b3", "grdb_colc1": 8])
XCTAssertEqual(rows[2].scopes.count, 0)
XCTAssertEqual(rows[3].unscoped, ["cold1": 13, "cold2": 9, "cold3": "d4"])
XCTAssertEqual(rows[3].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[3].prefetchedRows["bs"]!.count, 1)
XCTAssertEqual(rows[3].prefetchedRows["bs"]![0], ["colb1": 6, "colb2": 2, "colb3": "b3", "grdb_colc1": 9])
XCTAssertEqual(rows[3].scopes.count, 0)
XCTAssertEqual(rows[4].unscoped, ["cold1": 14, "cold2": nil, "cold3": "d5"])
XCTAssertEqual(rows[4].prefetchedRows.keys, ["bs"])
XCTAssertEqual(rows[4].prefetchedRows["bs"]!.count, 0)
XCTAssertEqual(rows[4].scopes.count, 0)
}
// Row.fetchOne
do {
let row = try Row.fetchOne(db, request)!
XCTAssertEqual(row.unscoped, ["cold1": 10, "cold2": 7, "cold3": "d1"])
XCTAssertEqual(row.prefetchedRows.keys, ["bs"])
XCTAssertEqual(row.prefetchedRows["bs"]!.count, 2)
XCTAssertEqual(row.prefetchedRows["bs"]![0], ["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colc1": 7])
XCTAssertEqual(row.prefetchedRows["bs"]![1], ["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colc1": 7])
XCTAssertEqual(row.scopes.count, 0)
}
}
}
}
func testEquatable() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let request1 = A
.orderByPrimaryKey()
let request2 = A
.including(all: A.hasMany(B.self).orderByPrimaryKey())
.orderByPrimaryKey()
let request3 = A
.including(all: A.hasMany(B.self).none())
.orderByPrimaryKey()
let row1 = try Row.fetchOne(db, request1)!
let row2 = try Row.fetchOne(db, request2)!
let row3 = try Row.fetchOne(db, request3)!
XCTAssertEqual(row1.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row2.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row3.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertTrue(row1.prefetchedRows.isEmpty)
XCTAssertFalse(row2.prefetchedRows.isEmpty)
XCTAssertFalse(row3.prefetchedRows.isEmpty)
XCTAssertNil(row1.prefetchedRows["bs"])
XCTAssertEqual(row2.prefetchedRows["bs"], [
["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colb2": 1],
["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colb2": 1]])
XCTAssertEqual(row3.prefetchedRows["bs"], [])
XCTAssertEqual(row1, row1)
XCTAssertEqual(row1, row1.copy())
XCTAssertEqual(row1, row1.unscoped)
XCTAssertEqual(row1, row2.unscoped)
XCTAssertEqual(row1, row3.unscoped)
XCTAssertNotEqual(row1, row2)
XCTAssertNotEqual(row1, row3)
XCTAssertEqual(row2, row2)
XCTAssertEqual(row2, row2.copy())
XCTAssertNotEqual(row2, row2.unscoped)
XCTAssertNotEqual(row2, row3)
XCTAssertEqual(row3, row3)
XCTAssertEqual(row3, row3.copy())
XCTAssertNotEqual(row3, row3.unscoped)
}
}
func testCopy() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.read { db in
let request1 = A
.orderByPrimaryKey()
let request2 = A
.including(all: A.hasMany(B.self).orderByPrimaryKey())
.orderByPrimaryKey()
let request3 = A
.including(all: A.hasMany(B.self).none())
.orderByPrimaryKey()
let row1 = try Row.fetchOne(db, request1)!.copy()
let row2 = try Row.fetchOne(db, request2)!.copy()
let row3 = try Row.fetchOne(db, request3)!.copy()
XCTAssertEqual(row1.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row2.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertEqual(row3.unscoped, ["cola1": 1, "cola2": "a1"])
XCTAssertTrue(row1.prefetchedRows.isEmpty)
XCTAssertFalse(row2.prefetchedRows.isEmpty)
XCTAssertFalse(row3.prefetchedRows.isEmpty)
XCTAssertNil(row1.prefetchedRows["bs"])
XCTAssertEqual(row2.prefetchedRows["bs"], [
["colb1": 4, "colb2": 1, "colb3": "b1", "grdb_colb2": 1],
["colb1": 5, "colb2": 1, "colb3": "b2", "grdb_colb2": 1]])
XCTAssertEqual(row3.prefetchedRows["bs"], [])
}
}
// Regression test for https://github.com/groue/GRDB.swift/issues/871
func testCompoundColumnLimit() throws {
struct Parent: Encodable, PersistableRecord {
let a: Int
let b: Int
static let children = hasMany(Child.self)
}
struct Child: Encodable, PersistableRecord {
let a: Int
let b: Int
}
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
try db.create(table: "parent") { t in
t.column("a", .integer).notNull()
t.column("b", .integer).notNull()
t.primaryKey(["a", "b"])
}
try db.create(table: "child") { t in
t.column("a", .integer).notNull()
t.column("b", .integer).notNull()
t.foreignKey(["a", "b"], references: "parent")
}
let count = Int(sqlite3_limit(db.sqliteConnection, SQLITE_LIMIT_EXPR_DEPTH, -1))
for index in 0..<count {
try Parent(a: index, b: 1).insert(db)
try Child(a: index, b: 1).insert(db)
}
let request = Parent
.including(all: Parent.children)
.asRequest(of: Row.self)
let rows = try request.fetchAll(db)
for (index, row) in rows.enumerated() {
XCTAssertEqual(row.unscoped, ["a": index, "b": 1])
XCTAssertEqual(row.prefetchedRows.keys, ["children"])
XCTAssertEqual(row.prefetchedRows["children"]!.count, 1)
XCTAssertEqual(
row.prefetchedRows["children"]![0],
["a": index, "b": 1, "grdb_a": index, "grdb_b": 1])
}
}
}
}