import XCTest import GRDB private struct A: TableRecord { static let b = belongsTo(B.self) static let c = hasOne(C.self, through: b, using: B.c) static let restrictedC = hasOne(RestrictedC.self, through: b, using: B.restrictedC) static let extendedC = hasOne(ExtendedC.self, through: b, using: B.extendedC) } private struct B: TableRecord { static let c = belongsTo(C.self) static let restrictedC = belongsTo(RestrictedC.self) static let extendedC = belongsTo(ExtendedC.self) } private struct C: TableRecord { } private struct RestrictedC : TableRecord { static let databaseTableName = "c" static let databaseSelection: [any SQLSelectable] = [Column("name")] } private struct ExtendedC : TableRecord { static let databaseTableName = "c" static let databaseSelection: [any SQLSelectable] = [AllColumns(), Column.rowID] } /// Test SQL generation class AssociationHasOneThroughSQLDerivationTests: 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") } try db.create(table: "a") { t in t.autoIncrementedPrimaryKey("id") t.belongsTo("b") } } } func testDefaultSelection() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try assertEqualSQL(db, A.including(required: A.c), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" """) try assertEqualSQL(db, A.including(required: A.restrictedC), """ SELECT "a".*, "c"."name" \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" """) try assertEqualSQL(db, A.including(required: A.extendedC), """ SELECT "a".*, "c".*, "c"."rowid" \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" """) } } func testCustomSelection() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in do { let request = A.including(required: A.c .select(Column("name"))) try assertEqualSQL(db, request, """ SELECT "a".*, "c"."name" \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" """) } do { let request = A.including(required: A.c .select( AllColumns(), Column.rowID)) try assertEqualSQL(db, request, """ SELECT "a".*, "c".*, "c"."rowid" \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" """) } do { let aAlias = TableAlias() let request = A .aliased(aAlias) .including(required: A.c .select( Column("name"), (Column("id") + aAlias[Column("id")]).forKey("foo"))) try assertEqualSQL(db, request, """ SELECT "a".*, "c"."name", "c"."id" + "a"."id" AS "foo" \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" """) } } } func testFilteredAssociationImpactsJoinOnClause() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in do { let request = A.including(required: A.c.filter(Column("name") != nil)) try assertEqualSQL(db, request, """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON ("c"."id" = "b"."cId") AND ("c"."name" IS NOT NULL) """) } do { let request = A.including(required: A.c.filter(key: 1)) try assertEqualSQL(db, request, """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON ("c"."id" = "b"."cId") AND ("c"."id" = 1) """) } do { let request = A.including(required: A.c.filter(keys: [1, 2, 3])) try assertEqualSQL(db, request, """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON ("c"."id" = "b"."cId") AND ("c"."id" IN (1, 2, 3)) """) } do { let request = A.including(required: A.c.filter(key: ["id": 1])) try assertEqualSQL(db, request, """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON ("c"."id" = "b"."cId") AND ("c"."id" = 1) """) } do { let request = A.including(required: A.c.filter(keys: [["id": 1], ["id": 2]])) try assertEqualSQL(db, request, """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON ("c"."id" = "b"."cId") AND (("c"."id" = 1) OR ("c"."id" = 2)) """) } do { let cAlias = TableAlias(name: "customC") let request = A.including(required: A.c .aliased(cAlias) .filter(sql: "customC.name = ?", arguments: ["foo"])) try assertEqualSQL(db, request, """ SELECT "a".*, "customC".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" "customC" ON ("customC"."id" = "b"."cId") AND (customC.name = 'foo') """) } } } func testFilterAssociationInWhereClause() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in let cAlias = TableAlias() let request = A .including(required: A.c.aliased(cAlias)) .filter(cAlias[Column("name")] != nil) try assertEqualSQL(db, request, """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ WHERE "c"."name" IS NOT NULL """) } } func testOrderAssociation() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try assertEqualSQL(db, A.including(required: A.c.order(Column("name"))), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "c"."name" """) try assertEqualSQL(db, A.including(required: A.c.orderByPrimaryKey()), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "c"."id" """) try assertEqualSQL(db, A.including(required: A.c.orderByPrimaryKey()).orderByPrimaryKey(), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "a"."id", "c"."id" """) try assertEqualSQL(db, A.including(required: A.c.orderByPrimaryKey().reversed()).orderByPrimaryKey(), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "a"."id", "c"."id" DESC """) try assertEqualSQL(db, A.including(required: A.c.order(Column("name"))).reversed(), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "c"."name" DESC """) try assertEqualSQL(db, A.including(required: A.c.orderByPrimaryKey()).reversed(), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "c"."id" DESC """) try assertEqualSQL(db, A.including(required: A.c.orderByPrimaryKey()).orderByPrimaryKey().reversed(), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "a"."id" DESC, "c"."id" DESC """) try assertEqualSQL(db, A.including(required: A.c.orderByPrimaryKey().reversed()).orderByPrimaryKey().reversed(), """ SELECT "a".*, "c".* \ FROM "a" \ JOIN "b" ON "b"."id" = "a"."bId" \ JOIN "c" ON "c"."id" = "b"."cId" \ ORDER BY "a"."id" DESC, "c"."id" """) } } }