284 lines
13 KiB
Swift
284 lines
13 KiB
Swift
import XCTest
|
|
import GRDB
|
|
|
|
private struct A: Codable, FetchableRecord, PersistableRecord {
|
|
static let defaultB = belongsTo(B.self)
|
|
static let defaultC = hasOne(C.self, through: defaultB, using: B.c)
|
|
static let customC1 = hasOne(C.self, through: defaultB.forKey("customB"), using: B.c)
|
|
static let customC2 = hasOne(C.self, through: defaultB, using: B.c).forKey("customC2")
|
|
static let customC3 = hasOne(C.self, through: defaultB, using: B.c.forKey("customC3"))
|
|
var id: Int64
|
|
var bId: Int64?
|
|
var name: String
|
|
}
|
|
|
|
private struct B: Codable, FetchableRecord, PersistableRecord {
|
|
static let c = belongsTo(C.self)
|
|
var id: Int64
|
|
var cId: Int64?
|
|
var name: String
|
|
}
|
|
|
|
private struct C: Codable, FetchableRecord, PersistableRecord {
|
|
var id: Int64
|
|
var name: String
|
|
}
|
|
|
|
/// Test row scopes
|
|
class AssociationHasOneThroughRowscopeTests: GRDBTestCase {
|
|
|
|
override func setup(_ dbWriter: some DatabaseWriter) throws {
|
|
try dbWriter.write { db in
|
|
try db.create(table: "c") { t in
|
|
t.autoIncrementedPrimaryKey("id")
|
|
t.column("name", .text)
|
|
}
|
|
try db.create(table: "b") { t in
|
|
t.autoIncrementedPrimaryKey("id")
|
|
t.belongsTo("c")
|
|
t.column("name", .text)
|
|
}
|
|
try db.create(table: "a") { t in
|
|
t.autoIncrementedPrimaryKey("id")
|
|
t.belongsTo("b")
|
|
t.column("name", .text)
|
|
}
|
|
|
|
try C(id: 1, name: "c1").insert(db)
|
|
try B(id: 1, cId: 1, name: "b1").insert(db)
|
|
try B(id: 2, cId: nil, name: "b2").insert(db)
|
|
try A(id: 1, bId: 1, name: "a1").insert(db)
|
|
try A(id: 2, bId: 2, name: "a2").insert(db)
|
|
try A(id: 3, bId: nil, name: "a3").insert(db)
|
|
}
|
|
}
|
|
|
|
func testJoiningDoesNotUseAnyRowAdapter() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
try dbQueue.inDatabase { db in
|
|
let request = A.joining(required: A.defaultC)
|
|
let adapter = try request.makePreparedRequest(db, forSingleResult: false).adapter
|
|
XCTAssertNil(adapter)
|
|
}
|
|
}
|
|
|
|
func testDefaultScopeIncludingRequired() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.including(required: A.defaultC).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertEqual(Set(rows[0].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[0].scopes["b"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.scopes["c"]!.unscoped, ["id":1, "name":"c1"])
|
|
}
|
|
|
|
func testDefaultScopeAnnotatedWithRequired() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.annotated(withRequired: A.defaultC).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "id":1, "name":"c1"])
|
|
}
|
|
|
|
func testDefaultScopeAnnotatedWithRequiredCustomSelection() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.annotated(withRequired: A.defaultC.select(Column("name").forKey("cName"))).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "cName":"c1"])
|
|
}
|
|
|
|
func testDefaultScopeIncludingOptional() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.including(optional: A.defaultC).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 3)
|
|
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertEqual(Set(rows[0].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[0].scopes["b"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.scopes["c"]!.unscoped, ["id":1, "name":"c1"])
|
|
|
|
XCTAssertEqual(rows[1].unscoped, ["id":2, "bId":2, "name":"a2"])
|
|
XCTAssertEqual(Set(rows[1].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[1].scopes["b"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[1].scopes["b"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[1].scopes["b"]!.scopes["c"]!.unscoped, ["id":nil, "name":nil])
|
|
|
|
XCTAssertEqual(rows[2].unscoped, ["id":3, "bId":nil, "name":"a3"])
|
|
XCTAssertEqual(Set(rows[2].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[2].scopes["b"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[2].scopes["b"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[2].scopes["b"]!.scopes["c"]!.unscoped, ["id":nil, "name":nil])
|
|
}
|
|
|
|
func testDefaultScopeAnnotatedWithOptional() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.annotated(withOptional: A.defaultC).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 3)
|
|
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "id":1, "name":"c1"])
|
|
XCTAssertEqual(rows[1], ["id":2, "bId":2, "name":"a2", "id":nil, "name":nil])
|
|
XCTAssertEqual(rows[2], ["id":3, "bId":nil, "name":"a3", "id":nil, "name":nil])
|
|
}
|
|
|
|
func testDefaultScopeAnnotatedWithOptionalCustomSelection() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.annotated(withOptional: A.defaultC.select(Column("name").forKey("cName"))).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 3)
|
|
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "cName":"c1"])
|
|
XCTAssertEqual(rows[1], ["id":2, "bId":2, "name":"a2", "cName":nil])
|
|
XCTAssertEqual(rows[2], ["id":3, "bId":nil, "name":"a3", "cName":nil])
|
|
}
|
|
|
|
func testDefaultScopeJoiningRequired() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.joining(required: A.defaultC).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertTrue(rows[0].scopes.names.isEmpty)
|
|
}
|
|
|
|
func testDefaultScopeJoiningOptional() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.joining(optional: A.defaultC).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 3)
|
|
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertTrue(rows[0].scopes.names.isEmpty)
|
|
|
|
XCTAssertEqual(rows[1].unscoped, ["id":2, "bId":2, "name":"a2"])
|
|
XCTAssertTrue(rows[1].scopes.names.isEmpty)
|
|
|
|
XCTAssertEqual(rows[2].unscoped, ["id":3, "bId":nil, "name":"a3"])
|
|
XCTAssertTrue(rows[2].scopes.names.isEmpty)
|
|
}
|
|
|
|
func testCustomC1() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.including(required: A.customC1).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertEqual(Set(rows[0].scopes.names), ["customB"])
|
|
XCTAssertEqual(rows[0].scopes["customB"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[0].scopes["customB"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[0].scopes["customB"]!.scopes["c"]!.unscoped, ["id":1, "name":"c1"])
|
|
}
|
|
|
|
func testCustomC2() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.including(required: A.customC2).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertEqual(Set(rows[0].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[0].scopes["b"]!.scopes.names), ["customC2"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.scopes["customC2"]!.unscoped, ["id":1, "name":"c1"])
|
|
}
|
|
|
|
func testCustomC3() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A.including(required: A.customC3).order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
XCTAssertEqual(rows.count, 1)
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertEqual(Set(rows[0].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.unscoped, [:])
|
|
XCTAssertEqual(Set(rows[0].scopes["b"]!.scopes.names), ["customC3"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.scopes["customC3"]!.unscoped, ["id":1, "name":"c1"])
|
|
}
|
|
|
|
func testDefaultScopeIncludingOptionalIncludingRequiredPivot() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
let request = A
|
|
.including(optional: A.defaultC)
|
|
.including(required: A.defaultB)
|
|
.order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 2)
|
|
|
|
XCTAssertEqual(rows[0].unscoped, ["id":1, "bId":1, "name":"a1"])
|
|
XCTAssertEqual(Set(rows[0].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.unscoped, ["id":1, "cId":1, "name":"b1"])
|
|
XCTAssertEqual(Set(rows[0].scopes["b"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[0].scopes["b"]!.scopes["c"]!.unscoped, ["id":1, "name":"c1"])
|
|
|
|
XCTAssertEqual(rows[1].unscoped, ["id":2, "bId":2, "name":"a2"])
|
|
XCTAssertEqual(Set(rows[1].scopes.names), ["b"])
|
|
XCTAssertEqual(rows[1].scopes["b"]!.unscoped, ["id":2, "cId":nil, "name":"b2"])
|
|
XCTAssertEqual(Set(rows[1].scopes["b"]!.scopes.names), ["c"])
|
|
XCTAssertEqual(rows[1].scopes["b"]!.scopes["c"]!.unscoped, ["id":nil, "name":nil])
|
|
}
|
|
|
|
func testDefaultScopeAnnotatedWithOptionalAnnotatedWithRequiredPivot() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
do {
|
|
let request = A
|
|
.annotated(withOptional: A.defaultC)
|
|
.annotated(withRequired: A.defaultB)
|
|
.order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 2)
|
|
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "id":1, "name":"c1", "id":1, "cId":1, "name":"b1"])
|
|
XCTAssertEqual(rows[1], ["id":2, "bId":2, "name":"a2", "id":nil, "name":nil, "id":2, "cId":nil, "name":"b2"])
|
|
}
|
|
do {
|
|
let request = A
|
|
.annotated(withRequired: A.defaultB)
|
|
.annotated(withOptional: A.defaultC)
|
|
.order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 2)
|
|
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "id":1, "cId":1, "name":"b1", "id":1, "name":"c1"])
|
|
XCTAssertEqual(rows[1], ["id":2, "bId":2, "name":"a2", "id":2, "cId":nil, "name":"b2", "id":nil, "name":nil])
|
|
}
|
|
}
|
|
|
|
func testDefaultScopeAnnotatedWithOptionalAnnotatedWithRequiredPivotCustomSelection() throws {
|
|
let dbQueue = try makeDatabaseQueue()
|
|
do {
|
|
let request = A
|
|
.annotated(withOptional: A.defaultC.select(Column("name").forKey("cName")))
|
|
.annotated(withRequired: A.defaultB.select(Column("name").forKey("bName")))
|
|
.order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 2)
|
|
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "cName":"c1", "bName":"b1"])
|
|
XCTAssertEqual(rows[1], ["id":2, "bId":2, "name":"a2", "cName":nil, "bName":"b2"])
|
|
}
|
|
do {
|
|
let request = A
|
|
.annotated(withRequired: A.defaultB.select(Column("name").forKey("bName")))
|
|
.annotated(withOptional: A.defaultC.select(Column("name").forKey("cName")))
|
|
.order(sql: "a.id")
|
|
let rows = try dbQueue.inDatabase { try Row.fetchAll($0, request) }
|
|
|
|
XCTAssertEqual(rows.count, 2)
|
|
|
|
XCTAssertEqual(rows[0], ["id":1, "bId":1, "name":"a1", "bName":"b1", "cName":"c1"])
|
|
XCTAssertEqual(rows[1], ["id":2, "bId":2, "name":"a2", "bName":"b2", "cName":nil])
|
|
}
|
|
}
|
|
}
|