diff --git a/Sources/StructuredQueries/Macros.swift b/Sources/StructuredQueries/Macros.swift index f042e01f..d1545cb5 100644 --- a/Sources/StructuredQueries/Macros.swift +++ b/Sources/StructuredQueries/Macros.swift @@ -114,7 +114,7 @@ public macro Column( /// - Parameters primaryKey: These columns are the table's composite primary key. @attached(peer) public macro Columns( - // as representableType: (any QueryRepresentable.Type)? = nil, + as representableType: (any QueryRepresentable.Type)? = nil, primaryKey: Bool = false ) = #externalMacro( diff --git a/Sources/StructuredQueriesCore/ColumnGroup.swift b/Sources/StructuredQueriesCore/ColumnGroup.swift index 2c182759..9aa91f6a 100644 --- a/Sources/StructuredQueriesCore/ColumnGroup.swift +++ b/Sources/StructuredQueriesCore/ColumnGroup.swift @@ -4,16 +4,16 @@ /// to generate values of this type. @dynamicMemberLookup public struct ColumnGroup: _TableColumnExpression -where Values.QueryOutput == Values { +where Values.QueryOutput: Table { public typealias Value = Values public var _names: [String] { Values.TableColumns.allColumns.map(\.name) } public typealias QueryValue = Values - public let keyPath: KeyPath + public let keyPath: KeyPath - public init(keyPath: KeyPath) { + public init(keyPath: KeyPath) { self.keyPath = keyPath } @@ -22,7 +22,7 @@ where Values.QueryOutput == Values { } public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> TableColumn { let column = Values.columns[keyPath: keyPath] return TableColumn( @@ -33,7 +33,7 @@ where Values.QueryOutput == Values { } public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> GeneratedColumn { let column = Values.columns[keyPath: keyPath] return GeneratedColumn( @@ -44,7 +44,7 @@ where Values.QueryOutput == Values { } public subscript( - dynamicMember keyPath: KeyPath> + dynamicMember keyPath: KeyPath> ) -> ColumnGroup { let column = Values.columns[keyPath: keyPath] return ColumnGroup( @@ -53,12 +53,12 @@ where Values.QueryOutput == Values { } public var _allColumns: [any TableColumnExpression] { - Values.TableColumns.allColumns.map { column in + Values.QueryOutput.TableColumns.allColumns.map { column in func open( _ column: some TableColumnExpression ) -> any TableColumnExpression { let keyPath = keyPath.appending( - path: unsafeDowncast(column.keyPath, to: KeyPath.self) + path: unsafeDowncast(column.keyPath, to: KeyPath.self) ) return TableColumn( column.name, @@ -71,12 +71,12 @@ where Values.QueryOutput == Values { } public var _writableColumns: [any WritableTableColumnExpression] { - Values.TableColumns.writableColumns.map { column in + Values.QueryOutput.TableColumns.writableColumns.map { column in func open( _ column: some WritableTableColumnExpression ) -> any WritableTableColumnExpression { let keyPath = keyPath.appending( - path: unsafeDowncast(column.keyPath, to: KeyPath.self) + path: unsafeDowncast(column.keyPath, to: KeyPath.self) ) return TableColumn( column.name, diff --git a/Sources/StructuredQueriesCore/TableAlias.swift b/Sources/StructuredQueriesCore/TableAlias.swift index 2caba556..91c1c3ec 100644 --- a/Sources/StructuredQueriesCore/TableAlias.swift +++ b/Sources/StructuredQueriesCore/TableAlias.swift @@ -252,7 +252,7 @@ where Base.TableColumns.PrimaryColumn: WritableTableColumnExpression { } extension TableAlias: QueryExpression where Base: QueryExpression { - public typealias QueryValue = Base.QueryValue + public typealias QueryValue = Self public var queryFragment: QueryFragment { base.queryFragment diff --git a/Sources/StructuredQueriesMacros/TableMacro.swift b/Sources/StructuredQueriesMacros/TableMacro.swift index 3d165a3b..02bf9747 100644 --- a/Sources/StructuredQueriesMacros/TableMacro.swift +++ b/Sources/StructuredQueriesMacros/TableMacro.swift @@ -868,9 +868,9 @@ extension TableMacro: ExtensionMacro { public typealias From = Swift.Never """, ]) - let columnWidth: ExprSyntax = """ + let columnWidth = """ var columnWidth = 0 - columnWidth += \(columnWidths, separator: "\ncolumnWidth += ") + columnWidth += \(columnWidths.map(\.description).joined(separator: "\ncolumnWidth += ")) return columnWidth """ @@ -881,7 +881,7 @@ extension TableMacro: ExtensionMacro { \(conformances.isEmpty ? "" : ": \(conformances, separator: ", ")") {\ \(statics, separator: "\n") public \(nonisolated)static var columns: TableColumns { TableColumns() } - public \(nonisolated)static var _columnWidth: Int { \(columnWidth) } + public \(nonisolated)static var _columnWidth: Int { \(raw: columnWidth) } public \(nonisolated)static var tableName: String { \(tableName) }\ \(letSchemaName)\(initDecoder)\(initFromOther) } diff --git a/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift b/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift index 47cb187c..6153828f 100644 --- a/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift +++ b/Tests/StructuredQueriesMacrosTests/TableMacroTests.swift @@ -2414,6 +2414,90 @@ extension SnapshotTests { } } + @Test func columnsRepresentation() { + assertMacro { + """ + @Selection + struct RemindersListAliasAndReminderCount { + @Columns(as: TableAlias.self) + let remindersList: RemindersList + let remindersCount: Int + } + """ + } expansion: { + #""" + struct RemindersListAliasAndReminderCount { + let remindersList: RemindersList + let remindersCount: Int + + public nonisolated struct TableColumns: StructuredQueriesCore.TableDefinition { + public typealias QueryValue = RemindersListAliasAndReminderCount + public let remindersList = StructuredQueriesCore.ColumnGroup>(keyPath: \QueryValue.remindersList) + public let remindersCount = StructuredQueriesCore._TableColumn.for("remindersCount", keyPath: \QueryValue.remindersCount) + public static var allColumns: [any StructuredQueriesCore.TableColumnExpression] { + var allColumns: [any StructuredQueriesCore.TableColumnExpression] = [] + allColumns.append(contentsOf: QueryValue.columns.remindersList._allColumns) + allColumns.append(contentsOf: QueryValue.columns.remindersCount._allColumns) + return allColumns + } + public static var writableColumns: [any StructuredQueriesCore.WritableTableColumnExpression] { + var writableColumns: [any StructuredQueriesCore.WritableTableColumnExpression] = [] + writableColumns.append(contentsOf: QueryValue.columns.remindersList._writableColumns) + writableColumns.append(contentsOf: QueryValue.columns.remindersCount._writableColumns) + return writableColumns + } + public var queryFragment: QueryFragment { + "\(self.remindersList), \(self.remindersCount)" + } + } + + public nonisolated struct Selection: StructuredQueriesCore.TableExpression { + public typealias QueryValue = RemindersListAliasAndReminderCount + public let allColumns: [any StructuredQueriesCore.QueryExpression] + public init( + remindersList: some StructuredQueriesCore.QueryExpression>, + remindersCount: some StructuredQueriesCore.QueryExpression + ) { + var allColumns: [any StructuredQueriesCore.QueryExpression] = [] + allColumns.append(contentsOf: remindersList._allColumns) + allColumns.append(contentsOf: remindersCount._allColumns) + self.allColumns = allColumns + } + } + } + + nonisolated extension RemindersListAliasAndReminderCount: StructuredQueriesCore.Table, StructuredQueriesCore._Selection, StructuredQueriesCore.PartialSelectStatement { + public typealias QueryValue = Self + public typealias From = Swift.Never + public nonisolated static var columns: TableColumns { + TableColumns() + } + public nonisolated static var _columnWidth: Int { + var columnWidth = 0 + columnWidth += TableAlias._columnWidth + columnWidth += Int._columnWidth + return columnWidth + } + public nonisolated static var tableName: String { + "remindersListAliasAndReminderCounts" + } + public nonisolated init(decoder: inout some StructuredQueriesCore.QueryDecoder) throws { + let remindersList = try decoder.decode(TableAlias.self) + let remindersCount = try decoder.decode(Int.self) + guard let remindersList else { + throw StructuredQueriesCore.QueryDecodingError.missingRequiredColumn + } + guard let remindersCount else { + throw StructuredQueriesCore.QueryDecodingError.missingRequiredColumn + } + self.remindersList = remindersList + self.remindersCount = remindersCount + } + } + """# + } + } + #if StructuredQueriesCasePaths @Test func enumBasics() { assertMacro { diff --git a/Tests/StructuredQueriesTests/SelectionTests.swift b/Tests/StructuredQueriesTests/SelectionTests.swift index 99812904..614c9cdb 100644 --- a/Tests/StructuredQueriesTests/SelectionTests.swift +++ b/Tests/StructuredQueriesTests/SelectionTests.swift @@ -234,6 +234,92 @@ extension SnapshotTests { """ } } + + @Test func alias() { + let baseQuery = + RemindersList.as(RL.self).all + .group(by: \.id) + .limit(2) + .join(Reminder.all) { $0.id.eq($1.remindersListID) } + + assertQuery( + baseQuery + .select { + RemindersListAliasAndReminderCount.Columns( + remindersList: $0, + remindersCount: $1.id.count() + ) + } + ) { + """ + SELECT "rLs"."id" AS "id", "rLs"."color" AS "color", "rLs"."title" AS "title", "rLs"."position" AS "position", count("reminders"."id") AS "remindersCount" + FROM "remindersLists" AS "rLs" + JOIN "reminders" ON ("rLs"."id") = ("reminders"."remindersListID") + GROUP BY "rLs"."id" + LIMIT 2 + """ + } results: { + """ + ┌─────────────────────────────────────┐ + │ RemindersListAliasAndReminderCount( │ + │ remindersList: RemindersList( │ + │ id: 1, │ + │ color: 4889071, │ + │ title: "Personal", │ + │ position: 0 │ + │ ), │ + │ remindersCount: 5 │ + │ ) │ + ├─────────────────────────────────────┤ + │ RemindersListAliasAndReminderCount( │ + │ remindersList: RemindersList( │ + │ id: 2, │ + │ color: 15567157, │ + │ title: "Family", │ + │ position: 0 │ + │ ), │ + │ remindersCount: 3 │ + │ ) │ + └─────────────────────────────────────┘ + """ + } + } + + @Test func optionalAlias() { + let baseQuery = + Reminder + .leftJoin(RemindersList.as(RL.self).all) { $0.remindersListID.eq($1.id) } + + assertQuery( + baseQuery + .select { + OptionalRemindersListAliasAndReminderCount.Columns( + remindersList: $1, + remindersCount: $0.id.count() + ) + } + ) { + """ + SELECT "rLs"."id" AS "id", "rLs"."color" AS "color", "rLs"."title" AS "title", "rLs"."position" AS "position", count("reminders"."id") AS "remindersCount" + FROM "reminders" + LEFT JOIN "remindersLists" AS "rLs" ON ("reminders"."remindersListID") = ("rLs"."id") + """ + } results: { + """ + ┌─────────────────────────────────────────────┐ + │ OptionalRemindersListAliasAndReminderCount( │ + │ remindersList: RemindersList( │ + │ id: 1, │ + │ color: 4889071, │ + │ title: "Personal", │ + │ position: 0 │ + │ ), │ + │ remindersCount: 10 │ + │ ) │ + └─────────────────────────────────────────────┘ + """ + } + } } } @@ -254,6 +340,22 @@ struct RemindersListAndReminderCount { let remindersCount: Int } +enum RL: AliasName {} + +@Selection +struct RemindersListAliasAndReminderCount { + @Columns(as: TableAlias.self) + let remindersList: RemindersList + let remindersCount: Int +} + +@Selection +struct OptionalRemindersListAliasAndReminderCount { + @Columns(as: TableAlias?.self) + let remindersList: RemindersList? + let remindersCount: Int +} + @Selection struct Stats { let completedCount: Int