From 69d655a54dc8ec18483ba6b4b7a97f4c76c09c79 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 16 Jun 2025 18:44:26 -0700 Subject: [PATCH 1/7] Draft improvements - Ability to call `find` - Inheritance of primary table's default scope --- .../StructuredQueriesCore/PrimaryKeyed.swift | 84 +++++++++++++++++-- .../PrimaryKeyedTableTests.swift | 32 +++++++ Tests/StructuredQueriesTests/TableTests.swift | 33 ++++++++ 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/Sources/StructuredQueriesCore/PrimaryKeyed.swift b/Sources/StructuredQueriesCore/PrimaryKeyed.swift index f523c738..802c7607 100644 --- a/Sources/StructuredQueriesCore/PrimaryKeyed.swift +++ b/Sources/StructuredQueriesCore/PrimaryKeyed.swift @@ -22,6 +22,12 @@ public protocol TableDraft: Table { init(_ primaryTable: PrimaryTable) } +extension TableDraft { + public static var all: SelectOf { + unsafeBitCast(PrimaryTable.all.asSelect(), to: SelectOf.self) + } +} + /// A type representing a database table's columns. /// /// Don't conform to this protocol directly. Instead, use the `@Table` and `@Column` macros to @@ -60,6 +66,22 @@ extension PrimaryKeyedTable { } } +extension TableDraft { + /// A where clause filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: A `WHERE` clause. + public static func find( + _ primaryKey: PrimaryTable.TableColumns.PrimaryKey.QueryOutput + ) -> Where { + Self.where { _ in + PrimaryTable.columns.primaryKey.eq( + PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } + } +} + extension Where where From: PrimaryKeyedTable { /// Adds a primary key condition to a where clause. /// @@ -70,6 +92,40 @@ extension Where where From: PrimaryKeyedTable { } } +extension Where where From: TableDraft { + /// Adds a primary key condition to a where clause. + /// + /// - Parameter primaryKey: A primary key. + /// - Returns: A where clause with the added primary key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.where { _ in + From.PrimaryTable.columns.primaryKey.eq( + From.PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } + } +} + +extension Select where From: PrimaryKeyedTable { + /// A select statement filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: A select statement filtered by the given key. + public func find(_ primaryKey: From.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.and(From.find(primaryKey)) + } +} + +extension Select where From: TableDraft { + /// A select statement filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: A select statement filtered by the given key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.and(From.find(primaryKey)) + } +} + extension Update where From: PrimaryKeyedTable { /// An update statement filtered by a primary key. /// @@ -80,6 +136,20 @@ extension Update where From: PrimaryKeyedTable { } } +extension Update where From: TableDraft { + /// An update statement filtered by a primary key. + /// + /// - Parameter primaryKey: A primary key identifying a table row. + /// - Returns: An update statement filtered by the given key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.where { _ in + From.PrimaryTable.columns.primaryKey.eq( + From.PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } + } +} + extension Delete where From: PrimaryKeyedTable { /// A delete statement filtered by a primary key. /// @@ -90,12 +160,16 @@ extension Delete where From: PrimaryKeyedTable { } } -extension Select where From: PrimaryKeyedTable { - /// A select statement filtered by a primary key. +extension Delete where From: TableDraft { + /// A delete statement filtered by a primary key. /// /// - Parameter primaryKey: A primary key identifying a table row. - /// - Returns: A select statement filtered by the given key. - public func find(_ primaryKey: From.TableColumns.PrimaryKey.QueryOutput) -> Self { - self.and(From.find(primaryKey)) + /// - Returns: A delete statement filtered by the given key. + public func find(_ primaryKey: From.PrimaryTable.TableColumns.PrimaryKey.QueryOutput) -> Self { + self.where { _ in + From.PrimaryTable.columns.primaryKey.eq( + From.PrimaryTable.TableColumns.PrimaryKey(queryOutput: primaryKey) + ) + } } } diff --git a/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift b/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift index 9f693704..6cbacf1d 100644 --- a/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift +++ b/Tests/StructuredQueriesTests/PrimaryKeyedTableTests.swift @@ -115,6 +115,22 @@ extension SnapshotTests { """ } + assertQuery( + Reminder.Draft.find(1).select { ($0.id, $0.title) } + ) { + """ + SELECT "reminders"."id", "reminders"."title" + FROM "reminders" + WHERE ("reminders"."id" = 1) + """ + } results: { + """ + ┌───┬─────────────┐ + │ 1 │ "Groceries" │ + └───┴─────────────┘ + """ + } + assertQuery( Reminder.select { ($0.id, $0.title) }.find(2) ) { @@ -130,6 +146,22 @@ extension SnapshotTests { └───┴───────────┘ """ } + + assertQuery( + Reminder.Draft.select { ($0.id, $0.title) }.find(2) + ) { + """ + SELECT "reminders"."id", "reminders"."title" + FROM "reminders" + WHERE ("reminders"."id" = 2) + """ + } results: { + """ + ┌───┬───────────┐ + │ 2 │ "Haircut" │ + └───┴───────────┘ + """ + } } @Test func findByIDWithJoin() { diff --git a/Tests/StructuredQueriesTests/TableTests.swift b/Tests/StructuredQueriesTests/TableTests.swift index 230d61f5..311d9a58 100644 --- a/Tests/StructuredQueriesTests/TableTests.swift +++ b/Tests/StructuredQueriesTests/TableTests.swift @@ -54,6 +54,23 @@ extension SnapshotTests { └─────────────────────────────────────────────┘ """ } + assertQuery(Row.Draft.where { $0.id > Optional(0) }) { + """ + SELECT "rows"."id", "rows"."isDeleted" + FROM "rows" + WHERE NOT ("rows"."isDeleted") AND ("rows"."id" > 0) + ORDER BY "rows"."id" DESC + """ + } results: { + """ + ┌───────────────────────────────────────────────────┐ + │ SnapshotTests.TableTests.DefaultSelect.Row.Draft( │ + │ id: 1, │ + │ isDeleted: false │ + │ ) │ + └───────────────────────────────────────────────────┘ + """ + } assertQuery(Row.select(\.id)) { """ SELECT "rows"."id" @@ -328,6 +345,22 @@ extension SnapshotTests { └────────────────────────────────────────────┘ """ } + assertQuery(Row.Draft.where { $0.id > Optional(0) }) { + """ + SELECT "rows"."id", "rows"."isDeleted" + FROM "rows" + WHERE NOT ("rows"."isDeleted") AND ("rows"."id" > 0) + """ + } results: { + """ + ┌──────────────────────────────────────────────────┐ + │ SnapshotTests.TableTests.DefaultWhere.Row.Draft( │ + │ id: 1, │ + │ isDeleted: false │ + │ ) │ + └──────────────────────────────────────────────────┘ + """ + } assertQuery(Row.unscoped) { """ SELECT "rows"."id", "rows"."isDeleted" From 68966b460af7879c0be0b37ec05ab1b995388032 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 16 Jun 2025 23:38:46 -0700 Subject: [PATCH 2/7] wip --- Sources/StructuredQueriesCore/Operators.swift | 8 +- .../StructuredQueriesCore/PrimaryKeyed.swift | 20 +++++ .../Statements/Where.swift | 7 ++ .../TableDefinition.swift | 1 + .../OperatorsTests.swift | 8 +- .../StructuredQueriesTests/SelectTests.swift | 77 ++++++++++++++++++- 6 files changed, 112 insertions(+), 9 deletions(-) diff --git a/Sources/StructuredQueriesCore/Operators.swift b/Sources/StructuredQueriesCore/Operators.swift index 0d7b5b3e..a11aafb5 100644 --- a/Sources/StructuredQueriesCore/Operators.swift +++ b/Sources/StructuredQueriesCore/Operators.swift @@ -192,7 +192,7 @@ public func == ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) ? "IS" : "=", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. @@ -202,7 +202,7 @@ public func != ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) ? "IS NOT" : "<>", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS NOT", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. @@ -211,7 +211,7 @@ public func == ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) || isNull(rhs) ? "IS" : "=", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. @@ -220,7 +220,7 @@ public func != ( lhs: any QueryExpression, rhs: some QueryExpression ) -> some QueryExpression { - BinaryOperator(lhs: lhs, operator: isNull(lhs) || isNull(rhs) ? "IS NOT" : "<>", rhs: rhs) + BinaryOperator(lhs: lhs, operator: "IS NOT", rhs: rhs) } // NB: This overload is required due to an overload resolution bug of 'Updates[dynamicMember:]'. diff --git a/Sources/StructuredQueriesCore/PrimaryKeyed.swift b/Sources/StructuredQueriesCore/PrimaryKeyed.swift index 802c7607..1adfb037 100644 --- a/Sources/StructuredQueriesCore/PrimaryKeyed.swift +++ b/Sources/StructuredQueriesCore/PrimaryKeyed.swift @@ -23,6 +23,18 @@ public protocol TableDraft: Table { } extension TableDraft { + public static subscript( + dynamicMember keyPath: KeyPath> + ) -> some Statement { + SQLQueryExpression("\(PrimaryTable.self[keyPath: keyPath])") + } + + public static subscript( + dynamicMember keyPath: KeyPath> + ) -> SelectOf { + unsafeBitCast(PrimaryTable.self[keyPath: keyPath].asSelect(), to: SelectOf.self) + } + public static var all: SelectOf { unsafeBitCast(PrimaryTable.all.asSelect(), to: SelectOf.self) } @@ -44,6 +56,14 @@ where QueryValue: PrimaryKeyedTable { var primaryKey: TableColumn { get } } +extension TableDefinition where QueryValue: TableDraft { + public subscript( + dynamicMember keyPath: KeyPath + ) -> Member { + QueryValue.PrimaryTable.columns[keyPath: keyPath] + } +} + extension PrimaryKeyedTableDefinition { /// A query expression representing the number of rows in this table. /// diff --git a/Sources/StructuredQueriesCore/Statements/Where.swift b/Sources/StructuredQueriesCore/Statements/Where.swift index f4c48bc8..5960d958 100644 --- a/Sources/StructuredQueriesCore/Statements/Where.swift +++ b/Sources/StructuredQueriesCore/Statements/Where.swift @@ -80,6 +80,13 @@ public struct Where { public subscript(dynamicMember keyPath: KeyPath) -> Self { self + From.self[keyPath: keyPath] } + + public subscript( + dynamicMember keyPath: KeyPath> + ) -> Self + where From: TableDraft { + self + unsafeBitCast(From.PrimaryTable.self[keyPath: keyPath], to: Self.self) + } #endif } diff --git a/Sources/StructuredQueriesCore/TableDefinition.swift b/Sources/StructuredQueriesCore/TableDefinition.swift index 38bae167..a979c637 100644 --- a/Sources/StructuredQueriesCore/TableDefinition.swift +++ b/Sources/StructuredQueriesCore/TableDefinition.swift @@ -2,6 +2,7 @@ /// /// Don't conform to this protocol directly. Instead, use the `@Table` and `@Column` macros to /// generate a conformance. +@dynamicMemberLookup public protocol TableDefinition: QueryExpression where QueryValue: Table { /// An array of this table's columns. static var allColumns: [any TableColumnExpression] { get } diff --git a/Tests/StructuredQueriesTests/OperatorsTests.swift b/Tests/StructuredQueriesTests/OperatorsTests.swift index 0e9c6f2b..e21840f0 100644 --- a/Tests/StructuredQueriesTests/OperatorsTests.swift +++ b/Tests/StructuredQueriesTests/OperatorsTests.swift @@ -24,12 +24,12 @@ extension SnapshotTests { } assertInlineSnapshot(of: Row.columns.a == Row.columns.c, as: .sql) { """ - ("rows"."a" = "rows"."c") + ("rows"."a" IS "rows"."c") """ } assertInlineSnapshot(of: Row.columns.a == Row.columns.a, as: .sql) { """ - ("rows"."a" = "rows"."a") + ("rows"."a" IS "rows"."a") """ } assertInlineSnapshot(of: Row.columns.a == nil as Int?, as: .sql) { @@ -64,12 +64,12 @@ extension SnapshotTests { } assertInlineSnapshot(of: Row.columns.a != Row.columns.c, as: .sql) { """ - ("rows"."a" <> "rows"."c") + ("rows"."a" IS NOT "rows"."c") """ } assertInlineSnapshot(of: Row.columns.a != Row.columns.a, as: .sql) { """ - ("rows"."a" <> "rows"."a") + ("rows"."a" IS NOT "rows"."a") """ } assertInlineSnapshot(of: Row.columns.a != nil as Int?, as: .sql) { diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index 03f1f404..8f8fd4a8 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -1126,7 +1126,7 @@ extension SnapshotTests { SELECT "remindersLists"."id", "remindersLists"."color", "remindersLists"."title", "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" FROM "remindersLists" LEFT JOIN "reminders" ON ("remindersLists"."id" = "reminders"."remindersListID") - WHERE ifnull(("reminders"."priority" = 3), 0) + WHERE ifnull(("reminders"."priority" IS 3), 0) """ } results: { """ @@ -1171,6 +1171,81 @@ extension SnapshotTests { } } + @Test func reusableStaticHelperOnDraft() { + assertQuery( + Reminder.Draft.incomplete.select(\.id) + ) { + """ + SELECT "reminders"."id" + FROM "reminders" + WHERE NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ + } + assertQuery( + Reminder.Draft.where { _ in true }.incomplete.select(\.id) + ) { + """ + SELECT "reminders"."id" + FROM "reminders" + WHERE 1 AND NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ + } + // TODO: Compile + // assertQuery( + // Reminder.Draft.select(\.id).incomplete + // ) + } + + @Test func reusableColumnHelperOnDraft() { + assertQuery( + Reminder.Draft.select(\.isHighPriority) + ) { + """ + SELECT ("reminders"."priority" IS 3) + FROM "reminders" + """ + } results: { + """ + ┌───────┐ + │ false │ + │ false │ + │ true │ + │ false │ + │ false │ + │ true │ + │ false │ + │ true │ + │ false │ + │ false │ + └───────┘ + """ + } + } + @Test func optionalMapAndFlatMap() { do { let query: some Statement = Reminder.select { From 8d4288dd575b4d754f262e3991cffb9772cebe69 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Jun 2025 00:11:38 -0700 Subject: [PATCH 3/7] wip --- .../Select+DynamicMemberLookup.swift | 233 ++++++++++++++++++ .../StructuredQueriesTests/SelectTests.swift | 126 +++++++++- 2 files changed, 355 insertions(+), 4 deletions(-) diff --git a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift index 6c10b31c..aa1d7c40 100644 --- a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift +++ b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift @@ -187,4 +187,237 @@ self + From.self[keyPath: keyPath] } } + + extension Select where From: TableDraft { + public subscript< + each C: QueryRepresentable, + each J: Table, + S: SelectStatement<(), From.PrimaryTable, ()> + >( + dynamicMember keyPath: KeyPath + ) -> Select<(repeat each C), From, (repeat each J)> + where Columns == (repeat each C), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath].asSelect(), + to: Select<(), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath> + ) -> Select<(repeat each C1, C2), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3), From.PrimaryTable, ()> + > + ) -> Select<(repeat each C1, C2, C3), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4), From.PrimaryTable, ()> + > + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4, C5), From.PrimaryTable, ()> + > + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J)> + where Columns == (repeat each C1), Joins == (repeat each J) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, ()>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath> + ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3), From.PrimaryTable, J2> + > + ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, J2>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4), From.PrimaryTable, J2> + > + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, J2>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J1: Table, + J2: Table + >( + dynamicMember + keyPath: KeyPath> + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, J2>.self + ) + } + +// public subscript< +// each C: QueryRepresentable, +// each J1: Table, +// J2: Table, +// J3: Table +// >( +// dynamicMember keyPath: KeyPath> +// ) -> Select<(repeat each C), From, (repeat each J1, J2, J3)> +// where Columns == (repeat each C), Joins == (repeat each J1) { +// self + From.PrimaryTable.self[keyPath: keyPath] +// } +// +// public subscript< +// each C1: QueryRepresentable, +// C2: QueryRepresentable, +// each J1: Table, +// J2: Table, +// J3: Table +// >( +// dynamicMember keyPath: KeyPath> +// ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2, J3)> +// where Columns == (repeat each C1), Joins == (repeat each J1) { +// self + From.PrimaryTable.self[keyPath: keyPath] +// } +// +// public subscript< +// each C1: QueryRepresentable, +// C2: QueryRepresentable, +// C3: QueryRepresentable, +// each J1: Table, +// J2: Table, +// J3: Table +// >( +// dynamicMember keyPath: KeyPath> +// ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> +// where Columns == (repeat each C1), Joins == (repeat each J1) { +// self + From.PrimaryTable.self[keyPath: keyPath] +// } +// +// public subscript< +// each C1: QueryRepresentable, +// C2: QueryRepresentable, +// C3: QueryRepresentable, +// C4: QueryRepresentable, +// each J1: Table, +// J2: Table, +// J3: Table +// >( +// dynamicMember keyPath: KeyPath> +// ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> +// where Columns == (repeat each C1), Joins == (repeat each J1) { +// self + From.PrimaryTable.self[keyPath: keyPath] +// } +// +// public subscript< +// each C1: QueryRepresentable, +// C2: QueryRepresentable, +// C3: QueryRepresentable, +// C4: QueryRepresentable, +// C5: QueryRepresentable, +// each J1: Table, +// J2: Table, +// J3: Table +// >( +// dynamicMember keyPath: KeyPath> +// ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> +// where Columns == (repeat each C1), Joins == (repeat each J1) { +// self + From.PrimaryTable.self[keyPath: keyPath] +// } + } #endif diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index 8f8fd4a8..cbf13d63 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -1214,10 +1214,128 @@ extension SnapshotTests { └───┘ """ } - // TODO: Compile - // assertQuery( - // Reminder.Draft.select(\.id).incomplete - // ) + assertQuery( + Reminder.Draft.select(\.id).incomplete.select(\.id) + ) { + """ + SELECT "reminders"."id", "reminders"."id" + FROM "reminders" + WHERE NOT ("reminders"."isCompleted") + """ + } results: { + """ + ┌───┬───┐ + │ 1 │ 1 │ + │ 2 │ 2 │ + │ 3 │ 3 │ + │ 5 │ 5 │ + │ 6 │ 6 │ + │ 8 │ 8 │ + │ 9 │ 9 │ + └───┴───┘ + """ + } + assertQuery( + Reminder.Draft.all.incomplete + ) { + """ + SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + FROM "reminders" + WHERE NOT ("reminders"."isCompleted") + """ + } results: { + #""" + ┌────────────────────────────────────────────┐ + │ Reminder.Draft( │ + │ id: 1, │ + │ assignedUserID: 1, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Milk, Eggs, Apples", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Groceries" │ + │ ) │ + ├────────────────────────────────────────────┤ + │ Reminder.Draft( │ + │ id: 2, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2000-12-30T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Haircut" │ + │ ) │ + ├────────────────────────────────────────────┤ + │ Reminder.Draft( │ + │ id: 3, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-01T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "Ask about diet", │ + │ priority: .high, │ + │ remindersListID: 1, │ + │ title: "Doctor appointment" │ + │ ) │ + ├────────────────────────────────────────────┤ + │ Reminder.Draft( │ + │ id: 5, │ + │ assignedUserID: nil, │ + │ dueDate: nil, │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: nil, │ + │ remindersListID: 1, │ + │ title: "Buy concert tickets" │ + │ ) │ + ├────────────────────────────────────────────┤ + │ Reminder.Draft( │ + │ id: 6, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-03T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: true, │ + │ notes: "", │ + │ priority: .high, │ + │ remindersListID: 2, │ + │ title: "Pick up kids from school" │ + │ ) │ + ├────────────────────────────────────────────┤ + │ Reminder.Draft( │ + │ id: 8, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-05T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: "", │ + │ priority: .high, │ + │ remindersListID: 2, │ + │ title: "Take out trash" │ + │ ) │ + ├────────────────────────────────────────────┤ + │ Reminder.Draft( │ + │ id: 9, │ + │ assignedUserID: nil, │ + │ dueDate: Date(2001-01-03T00:00:00.000Z), │ + │ isCompleted: false, │ + │ isFlagged: false, │ + │ notes: """ │ + │ Status of tax return │ + │ Expenses for next year │ + │ Changing payroll company │ + │ """, │ + │ priority: nil, │ + │ remindersListID: 3, │ + │ title: "Call accountant" │ + │ ) │ + └────────────────────────────────────────────┘ + """# + } } @Test func reusableColumnHelperOnDraft() { From dba9eef4b02443f18e853ec361a92b38a3f91237 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Jun 2025 00:12:34 -0700 Subject: [PATCH 4/7] wip --- .../StructuredQueriesTests/SelectTests.swift | 128 ++++-------------- 1 file changed, 24 insertions(+), 104 deletions(-) diff --git a/Tests/StructuredQueriesTests/SelectTests.swift b/Tests/StructuredQueriesTests/SelectTests.swift index cbf13d63..56e80668 100644 --- a/Tests/StructuredQueriesTests/SelectTests.swift +++ b/Tests/StructuredQueriesTests/SelectTests.swift @@ -1215,126 +1215,46 @@ extension SnapshotTests { """ } assertQuery( - Reminder.Draft.select(\.id).incomplete.select(\.id) + Reminder.Draft.select(\.id).incomplete ) { """ - SELECT "reminders"."id", "reminders"."id" + SELECT "reminders"."id" FROM "reminders" WHERE NOT ("reminders"."isCompleted") """ } results: { """ - ┌───┬───┐ - │ 1 │ 1 │ - │ 2 │ 2 │ - │ 3 │ 3 │ - │ 5 │ 5 │ - │ 6 │ 6 │ - │ 8 │ 8 │ - │ 9 │ 9 │ - └───┴───┘ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ """ } assertQuery( - Reminder.Draft.all.incomplete + Reminder.Draft.all.incomplete.select(\.id) ) { """ - SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title" + SELECT "reminders"."id" FROM "reminders" WHERE NOT ("reminders"."isCompleted") """ } results: { - #""" - ┌────────────────────────────────────────────┐ - │ Reminder.Draft( │ - │ id: 1, │ - │ assignedUserID: 1, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Milk, Eggs, Apples", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Groceries" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder.Draft( │ - │ id: 2, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2000-12-30T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Haircut" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder.Draft( │ - │ id: 3, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-01T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "Ask about diet", │ - │ priority: .high, │ - │ remindersListID: 1, │ - │ title: "Doctor appointment" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder.Draft( │ - │ id: 5, │ - │ assignedUserID: nil, │ - │ dueDate: nil, │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: nil, │ - │ remindersListID: 1, │ - │ title: "Buy concert tickets" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder.Draft( │ - │ id: 6, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-03T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: true, │ - │ notes: "", │ - │ priority: .high, │ - │ remindersListID: 2, │ - │ title: "Pick up kids from school" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder.Draft( │ - │ id: 8, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-05T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: "", │ - │ priority: .high, │ - │ remindersListID: 2, │ - │ title: "Take out trash" │ - │ ) │ - ├────────────────────────────────────────────┤ - │ Reminder.Draft( │ - │ id: 9, │ - │ assignedUserID: nil, │ - │ dueDate: Date(2001-01-03T00:00:00.000Z), │ - │ isCompleted: false, │ - │ isFlagged: false, │ - │ notes: """ │ - │ Status of tax return │ - │ Expenses for next year │ - │ Changing payroll company │ - │ """, │ - │ priority: nil, │ - │ remindersListID: 3, │ - │ title: "Call accountant" │ - │ ) │ - └────────────────────────────────────────────┘ - """# + """ + ┌───┐ + │ 1 │ + │ 2 │ + │ 3 │ + │ 5 │ + │ 6 │ + │ 8 │ + │ 9 │ + └───┘ + """ } } From 939b2f355af193abaec56fb926362d0b4c0d2e8c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Jun 2025 00:14:21 -0700 Subject: [PATCH 5/7] wip --- .../Select+DynamicMemberLookup.swift | 152 +++++++++--------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift index aa1d7c40..dd56fcf0 100644 --- a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift +++ b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift @@ -340,84 +340,86 @@ each J1: Table, J2: Table >( - dynamicMember - keyPath: KeyPath> + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4, C5), From.PrimaryTable, J2> + > ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2)> where Columns == (repeat each C1), Joins == (repeat each J1) { - self + unsafeBitCast( - From.PrimaryTable.self[keyPath: keyPath], - to: Select<(C2, C3, C4, C5), From, J2>.self - ) + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, J2>.self + ) } -// public subscript< -// each C: QueryRepresentable, -// each J1: Table, -// J2: Table, -// J3: Table -// >( -// dynamicMember keyPath: KeyPath> -// ) -> Select<(repeat each C), From, (repeat each J1, J2, J3)> -// where Columns == (repeat each C), Joins == (repeat each J1) { -// self + From.PrimaryTable.self[keyPath: keyPath] -// } -// -// public subscript< -// each C1: QueryRepresentable, -// C2: QueryRepresentable, -// each J1: Table, -// J2: Table, -// J3: Table -// >( -// dynamicMember keyPath: KeyPath> -// ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2, J3)> -// where Columns == (repeat each C1), Joins == (repeat each J1) { -// self + From.PrimaryTable.self[keyPath: keyPath] -// } -// -// public subscript< -// each C1: QueryRepresentable, -// C2: QueryRepresentable, -// C3: QueryRepresentable, -// each J1: Table, -// J2: Table, -// J3: Table -// >( -// dynamicMember keyPath: KeyPath> -// ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> -// where Columns == (repeat each C1), Joins == (repeat each J1) { -// self + From.PrimaryTable.self[keyPath: keyPath] -// } -// -// public subscript< -// each C1: QueryRepresentable, -// C2: QueryRepresentable, -// C3: QueryRepresentable, -// C4: QueryRepresentable, -// each J1: Table, -// J2: Table, -// J3: Table -// >( -// dynamicMember keyPath: KeyPath> -// ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> -// where Columns == (repeat each C1), Joins == (repeat each J1) { -// self + From.PrimaryTable.self[keyPath: keyPath] -// } -// -// public subscript< -// each C1: QueryRepresentable, -// C2: QueryRepresentable, -// C3: QueryRepresentable, -// C4: QueryRepresentable, -// C5: QueryRepresentable, -// each J1: Table, -// J2: Table, -// J3: Table -// >( -// dynamicMember keyPath: KeyPath> -// ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> -// where Columns == (repeat each C1), Joins == (repeat each J1) { -// self + From.PrimaryTable.self[keyPath: keyPath] -// } + // public subscript< + // each C: QueryRepresentable, + // each J1: Table, + // J2: Table, + // J3: Table + // >( + // dynamicMember keyPath: KeyPath> + // ) -> Select<(repeat each C), From, (repeat each J1, J2, J3)> + // where Columns == (repeat each C), Joins == (repeat each J1) { + // self + From.PrimaryTable.self[keyPath: keyPath] + // } + // + // public subscript< + // each C1: QueryRepresentable, + // C2: QueryRepresentable, + // each J1: Table, + // J2: Table, + // J3: Table + // >( + // dynamicMember keyPath: KeyPath> + // ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2, J3)> + // where Columns == (repeat each C1), Joins == (repeat each J1) { + // self + From.PrimaryTable.self[keyPath: keyPath] + // } + // + // public subscript< + // each C1: QueryRepresentable, + // C2: QueryRepresentable, + // C3: QueryRepresentable, + // each J1: Table, + // J2: Table, + // J3: Table + // >( + // dynamicMember keyPath: KeyPath> + // ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> + // where Columns == (repeat each C1), Joins == (repeat each J1) { + // self + From.PrimaryTable.self[keyPath: keyPath] + // } + // + // public subscript< + // each C1: QueryRepresentable, + // C2: QueryRepresentable, + // C3: QueryRepresentable, + // C4: QueryRepresentable, + // each J1: Table, + // J2: Table, + // J3: Table + // >( + // dynamicMember keyPath: KeyPath> + // ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> + // where Columns == (repeat each C1), Joins == (repeat each J1) { + // self + From.PrimaryTable.self[keyPath: keyPath] + // } + // + // public subscript< + // each C1: QueryRepresentable, + // C2: QueryRepresentable, + // C3: QueryRepresentable, + // C4: QueryRepresentable, + // C5: QueryRepresentable, + // each J1: Table, + // J2: Table, + // J3: Table + // >( + // dynamicMember keyPath: KeyPath> + // ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> + // where Columns == (repeat each C1), Joins == (repeat each J1) { + // self + From.PrimaryTable.self[keyPath: keyPath] + // } } #endif From 857d07662a9e1677848e08c3dd6f03a91fe8d5e6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Jun 2025 00:25:39 -0700 Subject: [PATCH 6/7] wip --- .../Select+DynamicMemberLookup.swift | 163 ++++++++++-------- 1 file changed, 94 insertions(+), 69 deletions(-) diff --git a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift index dd56fcf0..0b0c0ce7 100644 --- a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift +++ b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift @@ -352,74 +352,99 @@ ) } - // public subscript< - // each C: QueryRepresentable, - // each J1: Table, - // J2: Table, - // J3: Table - // >( - // dynamicMember keyPath: KeyPath> - // ) -> Select<(repeat each C), From, (repeat each J1, J2, J3)> - // where Columns == (repeat each C), Joins == (repeat each J1) { - // self + From.PrimaryTable.self[keyPath: keyPath] - // } - // - // public subscript< - // each C1: QueryRepresentable, - // C2: QueryRepresentable, - // each J1: Table, - // J2: Table, - // J3: Table - // >( - // dynamicMember keyPath: KeyPath> - // ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2, J3)> - // where Columns == (repeat each C1), Joins == (repeat each J1) { - // self + From.PrimaryTable.self[keyPath: keyPath] - // } - // - // public subscript< - // each C1: QueryRepresentable, - // C2: QueryRepresentable, - // C3: QueryRepresentable, - // each J1: Table, - // J2: Table, - // J3: Table - // >( - // dynamicMember keyPath: KeyPath> - // ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> - // where Columns == (repeat each C1), Joins == (repeat each J1) { - // self + From.PrimaryTable.self[keyPath: keyPath] - // } - // - // public subscript< - // each C1: QueryRepresentable, - // C2: QueryRepresentable, - // C3: QueryRepresentable, - // C4: QueryRepresentable, - // each J1: Table, - // J2: Table, - // J3: Table - // >( - // dynamicMember keyPath: KeyPath> - // ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> - // where Columns == (repeat each C1), Joins == (repeat each J1) { - // self + From.PrimaryTable.self[keyPath: keyPath] - // } - // - // public subscript< - // each C1: QueryRepresentable, - // C2: QueryRepresentable, - // C3: QueryRepresentable, - // C4: QueryRepresentable, - // C5: QueryRepresentable, - // each J1: Table, - // J2: Table, - // J3: Table - // >( - // dynamicMember keyPath: KeyPath> - // ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> - // where Columns == (repeat each C1), Joins == (repeat each J1) { - // self + From.PrimaryTable.self[keyPath: keyPath] - // } + public subscript< + each C: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(), From, (J2, J3)>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select + > + ) -> Select<(repeat each C1, C2), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, (J2, J3)>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember + keyPath: KeyPath> + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, (J2, J3)>.self + ) + } + + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember + keyPath: KeyPath> + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, (J2, J3)>.self + ) + } } #endif From fe511efb21591682da085acdb4bd586353c47fd0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Jun 2025 00:26:06 -0700 Subject: [PATCH 7/7] wip --- .../Select+DynamicMemberLookup.swift | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift index 0b0c0ce7..e6b49fd6 100644 --- a/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift +++ b/Sources/StructuredQueriesCore/Statements/Select+DynamicMemberLookup.swift @@ -402,49 +402,54 @@ > ) -> Select<(repeat each C1, C2, C3), From, (repeat each J1, J2, J3)> where Columns == (repeat each C1), Joins == (repeat each J1) { - self + unsafeBitCast( - From.PrimaryTable.self[keyPath: keyPath], - to: Select<(C2, C3), From, (J2, J3)>.self - ) + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3), From, (J2, J3)>.self + ) } - public subscript< - each C1: QueryRepresentable, - C2: QueryRepresentable, - C3: QueryRepresentable, - C4: QueryRepresentable, - each J1: Table, - J2: Table, - J3: Table - >( - dynamicMember - keyPath: KeyPath> - ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> - where Columns == (repeat each C1), Joins == (repeat each J1) { - self + unsafeBitCast( - From.PrimaryTable.self[keyPath: keyPath], - to: Select<(C2, C3, C4), From, (J2, J3)>.self - ) - } + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C1, C2, C3, C4), From, (repeat each J1, J2, J3)> + where Columns == (repeat each C1), Joins == (repeat each J1) { + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4), From, (J2, J3)>.self + ) + } - public subscript< - each C1: QueryRepresentable, - C2: QueryRepresentable, - C3: QueryRepresentable, - C4: QueryRepresentable, - C5: QueryRepresentable, - each J1: Table, - J2: Table, - J3: Table - >( - dynamicMember - keyPath: KeyPath> - ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> + public subscript< + each C1: QueryRepresentable, + C2: QueryRepresentable, + C3: QueryRepresentable, + C4: QueryRepresentable, + C5: QueryRepresentable, + each J1: Table, + J2: Table, + J3: Table + >( + dynamicMember keyPath: KeyPath< + From.PrimaryTable.Type, Select<(C2, C3, C4, C5), From.PrimaryTable, (J2, J3)> + > + ) -> Select<(repeat each C1, C2, C3, C4, C5), From, (repeat each J1, J2, J3)> where Columns == (repeat each C1), Joins == (repeat each J1) { - self + unsafeBitCast( - From.PrimaryTable.self[keyPath: keyPath], - to: Select<(C2, C3, C4, C5), From, (J2, J3)>.self - ) + self + + unsafeBitCast( + From.PrimaryTable.self[keyPath: keyPath], + to: Select<(C2, C3, C4, C5), From, (J2, J3)>.self + ) } } #endif