From 8ed30cd61aa73a353e5f46e18bff301045bba4e6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Jul 2026 15:06:11 -0700 Subject: [PATCH 1/4] Work around Swift parameter pack bug in `@FetchAll(sectionBy:)` Inline sectioned fetches don't currently work if the query includes a join due to a parameter pack bug. We can work around it in a way similar to workarounds present in StructuredQueries: overloads. - `Joins == ()` - `Joins: Table` - `Joins == (repeat each J)` --- Package.resolved | 4 +- Sources/SQLiteData/FetchAll+Sections.swift | 239 +++++++++++++++--- .../FetchAllSectionsTests.swift | 99 +++++--- 3 files changed, 257 insertions(+), 85 deletions(-) diff --git a/Package.resolved b/Package.resolved index 2cc93a52..c7f91d8f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "706feb7858a7f6c242879d137b8ee30926aa5b26", - "version" : "1.12.0" + "revision" : "8dc1fbf2f6255a73dec53b4648164884898db4c5", + "version" : "1.14.1" } }, { diff --git a/Sources/SQLiteData/FetchAll+Sections.swift b/Sources/SQLiteData/FetchAll+Sections.swift index 4cae5fe1..c8ddcc28 100644 --- a/Sources/SQLiteData/FetchAll+Sections.swift +++ b/Sources/SQLiteData/FetchAll+Sections.swift @@ -752,21 +752,18 @@ extension FetchAll { extension FetchAll { public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: some SelectStatement, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, repeat (each J).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil ) where Element == V.QueryOutput, V.QueryOutput: Sendable { - let statement: Select = statement.asSelect() - guard let sectioning = sectioning(From.columns, repeat (each J).columns) else { + guard let sectioning = sectioning(From.columns) else { self.init(wrappedValue: wrappedValue, statement, database: database) return } @@ -780,13 +777,11 @@ extension FetchAll { } public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: some SelectStatement, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, repeat (each J).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) @@ -794,8 +789,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - let statement: Select = statement.asSelect() - guard let sectioning = sectioning(From.columns, repeat (each J).columns) else { + guard let sectioning = sectioning(From.columns) else { self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) return } @@ -810,20 +804,17 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - _ statement: some SelectStatement, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, repeat (each J).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil ) async throws -> FetchSubscription where Element == V.QueryOutput, V.QueryOutput: Sendable { - let statement: Select = statement.asSelect() - guard let sectioning = sectioning(From.columns, repeat (each J).columns) else { + guard let sectioning = sectioning(From.columns) else { return try await load(statement, database: database) } return try await loadSections( @@ -836,12 +827,10 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - _ statement: some SelectStatement, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, repeat (each J).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) async throws -> FetchSubscription @@ -849,8 +838,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - let statement: Select = statement.asSelect() - guard let sectioning = sectioning(From.columns, repeat (each J).columns) else { + guard let sectioning = sectioning(From.columns) else { return try await load(statement, database: database, scheduler: scheduler) } return try await loadSections( @@ -962,18 +950,135 @@ extension FetchAll { } } +extension FetchAll { + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database) + return + } + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) + return + } + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: scheduler + ) + } + + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + return try await load(statement, database: database) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + return try await load(statement, database: database, scheduler: scheduler) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: scheduler + ) + } +} + #if canImport(SwiftUI) extension FetchAll { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, - each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: some SelectStatement, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, repeat (each J).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation ) @@ -993,13 +1098,10 @@ extension FetchAll { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, - each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - _ statement: some SelectStatement, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, repeat (each J).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation? ) async throws -> FetchSubscription @@ -1059,6 +1161,61 @@ extension FetchAll { scheduler: .animation(animation) ) } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + self.init( + wrappedValue: wrappedValue, + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation? + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + try await load( + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } } #endif @@ -1097,9 +1254,7 @@ public struct _Sectioning: Hashable, Sendable { } package init(_ orderingTerm: _OrderingTerm>) { - var orderingTerm = orderingTerm self.select = orderingTerm.baseQueryFragment - orderingTerm.baseQueryFragment = orderingTerm.base.queryFragment self.order = orderingTerm.queryFragment } } diff --git a/Tests/SQLiteDataTests/FetchAllSectionsTests.swift b/Tests/SQLiteDataTests/FetchAllSectionsTests.swift index 89180123..1e26ba7d 100644 --- a/Tests/SQLiteDataTests/FetchAllSectionsTests.swift +++ b/Tests/SQLiteDataTests/FetchAllSectionsTests.swift @@ -341,11 +341,13 @@ struct FetchAllSectionsTests { } @Test func selectionSectionBy() async throws { - let statement = + @FetchAll( SectionedReminder - .order(by: \.id) - .select { SectionedRow.Columns(title: $0.title, label: $0.category) } - @FetchAll(statement, sectionBy: { $0.category }) var rows + .order(by: \.id) + .select { SectionedRow.Columns(title: $0.title, label: $0.category) }, + sectionBy: { $0.category } + ) + var rows try await $rows.load() #expect(rows.map(\.title) == ["Groceries", "Dishes", "Laundry", "Standup", "Review"]) @@ -354,12 +356,14 @@ struct FetchAllSectionsTests { } @Test func joinedSectionBy() async throws { - let statement = + @FetchAll( SectionedReminder - .order(by: \.id) - .join(SectionedCategory.all) { $0.category.eq($1.name) } - .select { SectionedRow.Columns(title: $0.title, label: $1.label) } - @FetchAll(statement, sectionBy: { $1.label }) var rows + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .select { SectionedRow.Columns(title: $0.title, label: $1.label) }, + sectionBy: { $1.label } + ) + var rows try await $rows.load() #expect(rows.map(\.title) == ["Dishes", "Laundry", "Standup", "Review", "Groceries"]) @@ -369,40 +373,47 @@ struct FetchAllSectionsTests { } @Test func joinedDescendingSectionBy() async throws { - let statement = + @FetchAll( SectionedReminder - .order(by: \.id) - .join(SectionedCategory.all) { $0.category.eq($1.name) } - .select { SectionedRow.Columns(title: $0.title, label: $1.label) } - @FetchAll(statement, sectionBy: { $1.label.desc() }) var rows + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .select { SectionedRow.Columns(title: $0.title, label: $1.label) }, + sectionBy: { $1.label.desc() } + ) + var rows try await $rows.load() #expect($rows.sections.sectionNames == ["Out & About", "At Work", "At Home"]) } @Test func loadJoinedSectionBy() async throws { - let statement = + @FetchAll( SectionedReminder - .order(by: \.id) - .join(SectionedCategory.all) { $0.category.eq($1.name) } - .select { SectionedRow.Columns(title: $0.title, label: $1.label) } - @FetchAll(statement) var rows + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .select { SectionedRow.Columns(title: $0.title, label: $1.label) } + ) + var rows try await $rows.load() #expect($rows.sections.sectionNames == [nil]) - try await $rows.load(statement, sectionBy: { $1.label }) + try await $rows.load( + SectionedReminder + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .select { SectionedRow.Columns(title: $0.title, label: $1.label) }, + sectionBy: { $1.label } + ) #expect(rows.map(\.title) == ["Dishes", "Laundry", "Standup", "Review", "Groceries"]) #expect($rows.sections.sectionNames == ["At Home", "At Work", "Out & About"]) } @Test(arguments: [true, false]) func dynamicJoinedSectionBy(isSectioned: Bool) async throws { - let statement = - SectionedReminder - .order(by: \.id) - .join(SectionedCategory.all) { $0.category.eq($1.name) } - .select { SectionedRow.Columns(title: $0.title, label: $1.label) } @FetchAll( - statement, + SectionedReminder + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .select { SectionedRow.Columns(title: $0.title, label: $1.label) }, sectionBy: { if isSectioned { $1.label @@ -420,13 +431,16 @@ struct FetchAllSectionsTests { } @Test func twoJoinsSectionBy() async throws { - let statement = + @FetchAll( SectionedReminder - .order(by: \.id) - .join(SectionedCategory.all) { $0.category.eq($1.name) } - .join(SectionedPriority.all) { reminder, _, priority in reminder.priority.eq(priority.name) } - .select { SectionedRow.Columns(title: $0.title, label: $2.label) } - @FetchAll(statement, sectionBy: { $2.label }) var rows + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .join(SectionedPriority.all) { reminder, _, priority in reminder.priority.eq(priority.name) + } + .select { SectionedRow.Columns(title: $0.title, label: $2.label) }, + sectionBy: { $2.label } + ) + var rows try await $rows.load() #expect(rows.map(\.title) == ["Dishes", "Standup", "Groceries"]) @@ -436,16 +450,19 @@ struct FetchAllSectionsTests { } @Test func threeJoinsSectionBy() async throws { - let statement = + @FetchAll( SectionedReminder - .order(by: \.id) - .join(SectionedCategory.all) { $0.category.eq($1.name) } - .join(SectionedPriority.all) { reminder, _, priority in reminder.priority.eq(priority.name) } - .join(SectionedUrgency.all) { reminder, _, _, urgency in - reminder.priority.eq(urgency.name) - } - .select { SectionedRow.Columns(title: $0.title, label: $3.label) } - @FetchAll(statement, sectionBy: { $3.label }) var rows + .order(by: \.id) + .join(SectionedCategory.all) { $0.category.eq($1.name) } + .join(SectionedPriority.all) { reminder, _, priority in reminder.priority.eq(priority.name) + } + .join(SectionedUrgency.all) { reminder, _, _, urgency in + reminder.priority.eq(urgency.name) + } + .select { SectionedRow.Columns(title: $0.title, label: $3.label) }, + sectionBy: { $3.label } + ) + var rows try await $rows.load() #expect(rows.map(\.title) == ["Groceries", "Dishes", "Standup"]) From 91dfb41e9716967646267df9a101f62b3ec3e7f9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Jul 2026 16:27:22 -0700 Subject: [PATCH 2/4] Organize --- Sources/SQLiteData/FetchAll+Sections.swift | 1150 ++++++++++---------- 1 file changed, 564 insertions(+), 586 deletions(-) diff --git a/Sources/SQLiteData/FetchAll+Sections.swift b/Sources/SQLiteData/FetchAll+Sections.swift index c8ddcc28..1d78bd89 100644 --- a/Sources/SQLiteData/FetchAll+Sections.swift +++ b/Sources/SQLiteData/FetchAll+Sections.swift @@ -37,52 +37,6 @@ extension FetchAll { return sectionedReader.wrappedValue } - fileprivate init( - wrappedValue: [Element], - statement: Select<(), From, ()>, - sectionBy: _Sectioning, - database: (any DatabaseReader)?, - scheduler: (any ValueObservationScheduler & Hashable)? - ) - where - Element == From.QueryOutput, - From.QueryOutput: Sendable - { - self.init( - wrappedValue: wrappedValue, - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectionBy), - sectionBy: sectionBy, - database: database, - scheduler: scheduler - ) - } - - fileprivate init( - wrappedValue: [Element], - request: FetchAllSectionedStatementValueRequest, - sectionBy: _Sectioning, - database: (any DatabaseReader)?, - scheduler: (any ValueObservationScheduler & Hashable)? - ) - where - Element == Value.QueryOutput, - Value.QueryOutput: Sendable - { - let sectionedReader = SharedReader( - wrappedValue: ResultsSectionCollection(elements: wrappedValue, sectionName: nil), - FetchKey( - request: request, - database: database, - scheduler: scheduler - ) - ) - self.sectionedReader = sectionedReader - self.sharedReader = sectionedReader.elements - self.sectioning.setValue(sectionBy) - } -} - -extension FetchAll { /// Initializes this property with a query that fetches every row from a table, grouping results /// into sections. /// @@ -179,30 +133,21 @@ extension FetchAll { /// /// - Parameters: /// - wrappedValue: A default collection to associate with this property. - /// - sectioning: A closure that returns a string expression, or an ordering of one, to group - /// results by, or `nil` for no grouping. + /// - sectionKeyPath: A key path to a string column to group results by. /// - database: The database to read from. A value of `nil` will use the default database /// (`@Dependency(\.defaultDatabase)`). - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. public init( wrappedValue: [Element] = [], - @_SectionBuilder sectionBy sectioning: (Element.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable + sectionBy sectionKeyPath: KeyPath< + Element.TableColumns, some QueryExpression> + >, + database: (any DatabaseReader)? = nil ) where Element: StructuredQueriesCore.Table, Element.QueryOutput == Element { - let statement: Select<(), Element, ()> = Element.all.asSelect() - guard let sectioning = sectioning(Element.columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) - return - } self.init( wrappedValue: wrappedValue, - statement: statement, - sectionBy: sectioning, - database: database, - scheduler: scheduler + sectionBy: { $0[keyPath: sectionKeyPath] }, + database: database ) } @@ -212,18 +157,16 @@ extension FetchAll { /// - Parameters: /// - wrappedValue: A default collection to associate with this property. /// - statement: A query associated with the wrapped value. - /// - sectioning: A closure that returns a string expression, or an ordering of one, to group - /// results by, or `nil` for no grouping. + /// - sectionKeyPath: A key path to a string column to group results by. /// - database: The database to read from. A value of `nil` will use the default database /// (`@Dependency(\.defaultDatabase)`). - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. public init( wrappedValue: [Element] = [], _ statement: S, - @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable + sectionBy sectionKeyPath: KeyPath< + S.From.TableColumns, some QueryExpression> + >, + database: (any DatabaseReader)? = nil ) where Element == S.From.QueryOutput, @@ -231,88 +174,94 @@ extension FetchAll { S.From.QueryOutput: Sendable, S.Joins == () { - let statement: Select<(), S.From, ()> = statement.asSelect() - guard let sectioning = sectioning(S.From.columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) + self.init( + wrappedValue: wrappedValue, + statement, + sectionBy: { $0[keyPath: sectionKeyPath] }, + database: database + ) + } + + public init< + V: QueryRepresentable, From: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database) return } self.init( wrappedValue: wrappedValue, - statement: statement, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), sectionBy: sectioning, database: database, - scheduler: scheduler + scheduler: nil ) } -} -#if canImport(SwiftUI) - extension FetchAll { - /// Initializes this property with a query that fetches every row from a table, grouping - /// results into sections. - /// - /// - Parameters: - /// - wrappedValue: A default collection to associate with this property. - /// - sectioning: A closure that returns a string expression, or an ordering of one, to - /// group results by, or `nil` for no grouping. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - animation: The animation to use for user interface changes that result from changes to - /// the fetched results. - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - public init( - wrappedValue: [Element] = [], - @_SectionBuilder sectionBy sectioning: (Element.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - animation: Animation - ) - where Element: StructuredQueriesCore.Table, Element.QueryOutput == Element { - self.init( - wrappedValue: wrappedValue, - sectionBy: sectioning, - database: database, - scheduler: .animation(animation) - ) + public init< + V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J.columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database) + return } - - /// Initializes this property with a query associated with the wrapped value, grouping results - /// into sections. - /// - /// - Parameters: - /// - wrappedValue: A default collection to associate with this property. - /// - statement: A query associated with the wrapped value. - /// - sectioning: A closure that returns a string expression, or an ordering of one, to - /// group results by, or `nil` for no grouping. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - animation: The animation to use for user interface changes that result from changes to - /// the fetched results. - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - public init( - wrappedValue: [Element] = [], - _ statement: S, - @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - animation: Animation + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil ) - where - Element == S.From.QueryOutput, - S.QueryValue == (), - S.From.QueryOutput: Sendable, - S.Joins == () - { - self.init( - wrappedValue: wrappedValue, - statement, - sectionBy: sectioning, - database: database, - scheduler: .animation(animation) - ) + } + + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database) + return } + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) } -#endif -extension FetchAll { /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -347,19 +296,17 @@ extension FetchAll { /// /// - Parameters: /// - statement: A query associated with the wrapped value. - /// - sectioning: A closure that returns a string expression, or an ordering of one, to group - /// results by, or `nil` for no grouping. + /// - sectionKeyPath: A key path to a string column to group results by. /// - database: The database to read from. A value of `nil` will use the default database /// (`@Dependency(\.defaultDatabase)`). - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. /// - Returns: A subscription associated with the observation. @discardableResult public func load( _ statement: S, - @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable + sectionBy sectionKeyPath: KeyPath< + S.From.TableColumns, some QueryExpression> + >, + database: (any DatabaseReader)? = nil ) async throws -> FetchSubscription where Element == S.From.QueryOutput, @@ -367,26 +314,142 @@ extension FetchAll { S.From.QueryOutput: Sendable, S.Joins == () { - let statement: Select<(), S.From, ()> = statement.asSelect() - return try await loadSections( - statement: statement, - sectionBy: sectioning(S.From.columns), - database: database, - scheduler: scheduler + try await load( + statement, + sectionBy: { $0[keyPath: sectionKeyPath] }, + database: database ) } - func loadSections( - statement: Select<(), From, ()>, - sectionBy sectioning: _Sectioning?, - database: (any DatabaseReader)?, - scheduler: (any ValueObservationScheduler & Hashable)? - ) async throws -> FetchSubscription - where - Element == From.QueryOutput, - From.QueryOutput: Sendable - { - guard let sectioning else { + @discardableResult + public func load< + V: QueryRepresentable, From: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns) else { + return try await load(statement, database: database) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + + @discardableResult + public func load< + V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J.columns) else { + return try await load(statement, database: database) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + return try await load(statement, database: database) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + + fileprivate init( + wrappedValue: [Element], + statement: Select<(), From, ()>, + sectionBy: _Sectioning, + database: (any DatabaseReader)?, + scheduler: (any ValueObservationScheduler & Hashable)? + ) + where + Element == From.QueryOutput, + From.QueryOutput: Sendable + { + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectionBy), + sectionBy: sectionBy, + database: database, + scheduler: scheduler + ) + } + + fileprivate init( + wrappedValue: [Element], + request: FetchAllSectionedStatementValueRequest, + sectionBy: _Sectioning, + database: (any DatabaseReader)?, + scheduler: (any ValueObservationScheduler & Hashable)? + ) + where + Element == Value.QueryOutput, + Value.QueryOutput: Sendable + { + let sectionedReader = SharedReader( + wrappedValue: ResultsSectionCollection(elements: wrappedValue, sectionName: nil), + FetchKey( + request: request, + database: database, + scheduler: scheduler + ) + ) + self.sectionedReader = sectionedReader + self.sharedReader = sectionedReader.elements + self.sectioning.setValue(sectionBy) + } + + func loadSections( + statement: Select<(), From, ()>, + sectionBy sectioning: _Sectioning?, + database: (any DatabaseReader)?, + scheduler: (any ValueObservationScheduler & Hashable)? + ) async throws -> FetchSubscription + where + Element == From.QueryOutput, + From.QueryOutput: Sendable + { + guard let sectioning else { removeSections() let statement: Select = statement.selectStar() try await sharedReader.load( @@ -431,64 +494,36 @@ extension FetchAll { } } -#if canImport(SwiftUI) - extension FetchAll { - /// Replaces the wrapped value with data from the given query, grouping results into sections. - /// - /// - Parameters: - /// - statement: A query associated with the wrapped value. - /// - sectioning: A closure that returns a string expression, or an ordering of one, to - /// group results by, or `nil` for no grouping. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - animation: The animation to use for user interface changes that result from changes to - /// the fetched results. - /// - Returns: A subscription associated with the observation. - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @discardableResult - public func load( - _ statement: S, - @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - animation: Animation? - ) async throws -> FetchSubscription - where - Element == S.From.QueryOutput, - S.QueryValue == (), - S.From.QueryOutput: Sendable, - S.Joins == () - { - try await load( - statement, - sectionBy: sectioning, - database: database, - scheduler: .animation(animation) - ) - } - } -#endif - extension FetchAll { /// Initializes this property with a query that fetches every row from a table, grouping results /// into sections. /// /// - Parameters: /// - wrappedValue: A default collection to associate with this property. - /// - sectionKeyPath: A key path to a string column to group results by. + /// - sectioning: A closure that returns a string expression, or an ordering of one, to group + /// results by, or `nil` for no grouping. /// - database: The database to read from. A value of `nil` will use the default database /// (`@Dependency(\.defaultDatabase)`). + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. public init( wrappedValue: [Element] = [], - sectionBy sectionKeyPath: KeyPath< - Element.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil + @_SectionBuilder sectionBy sectioning: (Element.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable ) where Element: StructuredQueriesCore.Table, Element.QueryOutput == Element { + let statement: Select<(), Element, ()> = Element.all.asSelect() + guard let sectioning = sectioning(Element.columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) + return + } self.init( wrappedValue: wrappedValue, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database + statement: statement, + sectionBy: sectioning, + database: database, + scheduler: scheduler ) } @@ -498,16 +533,18 @@ extension FetchAll { /// - Parameters: /// - wrappedValue: A default collection to associate with this property. /// - statement: A query associated with the wrapped value. - /// - sectionKeyPath: A key path to a string column to group results by. + /// - sectioning: A closure that returns a string expression, or an ordering of one, to group + /// results by, or `nil` for no grouping. /// - database: The database to read from. A value of `nil` will use the default database /// (`@Dependency(\.defaultDatabase)`). + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. public init( wrappedValue: [Element] = [], _ statement: S, - sectionBy sectionKeyPath: KeyPath< - S.From.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil + @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable ) where Element == S.From.QueryOutput, @@ -515,11 +552,17 @@ extension FetchAll { S.From.QueryOutput: Sendable, S.Joins == () { + let statement: Select<(), S.From, ()> = statement.asSelect() + guard let sectioning = sectioning(S.From.columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) + return + } self.init( wrappedValue: wrappedValue, - statement, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database + statement: statement, + sectionBy: sectioning, + database: database, + scheduler: scheduler ) } @@ -585,209 +628,18 @@ extension FetchAll { ) } - /// Replaces the wrapped value with data from the given query, grouping results into sections. - /// - /// - Parameters: - /// - statement: A query associated with the wrapped value. - /// - sectionKeyPath: A key path to a string column to group results by. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - Returns: A subscription associated with the observation. - @discardableResult - public func load( - _ statement: S, - sectionBy sectionKeyPath: KeyPath< - S.From.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil - ) async throws -> FetchSubscription + public init< + V: QueryRepresentable, From: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) where - Element == S.From.QueryOutput, - S.QueryValue == (), - S.From.QueryOutput: Sendable, - S.Joins == () - { - try await load( - statement, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database - ) - } - - /// Replaces the wrapped value with data from the given query, grouping results into sections. - /// - /// - Parameters: - /// - statement: A query associated with the wrapped value. - /// - sectionKeyPath: A key path to a string column to group results by. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. - /// - Returns: A subscription associated with the observation. - @discardableResult - public func load( - _ statement: S, - sectionBy sectionKeyPath: KeyPath< - S.From.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable - ) async throws -> FetchSubscription - where - Element == S.From.QueryOutput, - S.QueryValue == (), - S.From.QueryOutput: Sendable, - S.Joins == () - { - try await load( - statement, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database, - scheduler: scheduler - ) - } -} - -#if canImport(SwiftUI) - extension FetchAll { - /// Initializes this property with a query that fetches every row from a table, grouping - /// results into sections. - /// - /// - Parameters: - /// - wrappedValue: A default collection to associate with this property. - /// - sectionKeyPath: A key path to a string column to group results by. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - animation: The animation to use for user interface changes that result from changes to - /// the fetched results. - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - public init( - wrappedValue: [Element] = [], - sectionBy sectionKeyPath: KeyPath< - Element.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil, - animation: Animation - ) - where Element: StructuredQueriesCore.Table, Element.QueryOutput == Element { - self.init( - wrappedValue: wrappedValue, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database, - animation: animation - ) - } - - /// Initializes this property with a query associated with the wrapped value, grouping results - /// into sections. - /// - /// - Parameters: - /// - wrappedValue: A default collection to associate with this property. - /// - statement: A query associated with the wrapped value. - /// - sectionKeyPath: A key path to a string column to group results by. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - animation: The animation to use for user interface changes that result from changes to - /// the fetched results. - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - public init( - wrappedValue: [Element] = [], - _ statement: S, - sectionBy sectionKeyPath: KeyPath< - S.From.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil, - animation: Animation - ) - where - Element == S.From.QueryOutput, - S.QueryValue == (), - S.From.QueryOutput: Sendable, - S.Joins == () - { - self.init( - wrappedValue: wrappedValue, - statement, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database, - animation: animation - ) - } - - /// Replaces the wrapped value with data from the given query, grouping results into sections. - /// - /// - Parameters: - /// - statement: A query associated with the wrapped value. - /// - sectionKeyPath: A key path to a string column to group results by. - /// - database: The database to read from. A value of `nil` will use the default database - /// (`@Dependency(\.defaultDatabase)`). - /// - animation: The animation to use for user interface changes that result from changes to - /// the fetched results. - /// - Returns: A subscription associated with the observation. - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @discardableResult - public func load( - _ statement: S, - sectionBy sectionKeyPath: KeyPath< - S.From.TableColumns, some QueryExpression> - >, - database: (any DatabaseReader)? = nil, - animation: Animation? - ) async throws -> FetchSubscription - where - Element == S.From.QueryOutput, - S.QueryValue == (), - S.From.QueryOutput: Sendable, - S.Joins == () - { - try await load( - statement, - sectionBy: { $0[keyPath: sectionKeyPath] }, - database: database, - animation: animation - ) - } - } -#endif - -extension FetchAll { - public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table - >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil - ) - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database) - return - } - self.init( - wrappedValue: wrappedValue, - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: nil - ) - } - - public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table - >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable - ) - where - Element == V.QueryOutput, - V.QueryOutput: Sendable + Element == V.QueryOutput, + V.QueryOutput: Sendable { guard let sectioning = sectioning(From.columns) else { self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) @@ -802,69 +654,21 @@ extension FetchAll { ) } - @discardableResult - public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil - ) async throws -> FetchSubscription - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns) else { - return try await load(statement, database: database) - } - return try await loadSections( - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: nil - ) - } - - @discardableResult - public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable - ) async throws -> FetchSubscription - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns) else { - return try await load(statement, database: database, scheduler: scheduler) - } - return try await loadSections( - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: scheduler - ) - } -} - -extension FetchAll { public init< V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], _ statement: Select, @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable ) where Element == V.QueryOutput, V.QueryOutput: Sendable { guard let sectioning = sectioning(From.columns, J.columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database) + self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) return } self.init( @@ -872,16 +676,21 @@ extension FetchAll { request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), sectionBy: sectioning, database: database, - scheduler: nil + scheduler: scheduler ) } public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) @@ -889,7 +698,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns, J.columns) else { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) return } @@ -902,109 +711,89 @@ extension FetchAll { ) } + /// Replaces the wrapped value with data from the given query, grouping results into sections. + /// + /// - Parameters: + /// - statement: A query associated with the wrapped value. + /// - sectioning: A closure that returns a string expression, or an ordering of one, to group + /// results by, or `nil` for no grouping. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. + /// - Returns: A subscription associated with the observation. @discardableResult - public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, - database: (any DatabaseReader)? = nil + public func load( + _ statement: S, + @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable ) async throws -> FetchSubscription where - Element == V.QueryOutput, - V.QueryOutput: Sendable + Element == S.From.QueryOutput, + S.QueryValue == (), + S.From.QueryOutput: Sendable, + S.Joins == () { - guard let sectioning = sectioning(From.columns, J.columns) else { - return try await load(statement, database: database) - } + let statement: Select<(), S.From, ()> = statement.asSelect() return try await loadSections( - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, + statement: statement, + sectionBy: sectioning(S.From.columns), database: database, - scheduler: nil + scheduler: scheduler ) } + /// Replaces the wrapped value with data from the given query, grouping results into sections. + /// + /// - Parameters: + /// - statement: A query associated with the wrapped value. + /// - sectionKeyPath: A key path to a string column to group results by. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. + /// - Returns: A subscription associated with the observation. @discardableResult - public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + public func load( + _ statement: S, + sectionBy sectionKeyPath: KeyPath< + S.From.TableColumns, some QueryExpression> + >, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) async throws -> FetchSubscription where - Element == V.QueryOutput, - V.QueryOutput: Sendable + Element == S.From.QueryOutput, + S.QueryValue == (), + S.From.QueryOutput: Sendable, + S.Joins == () { - guard let sectioning = sectioning(From.columns, J.columns) else { - return try await load(statement, database: database, scheduler: scheduler) - } - return try await loadSections( - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, + try await load( + statement, + sectionBy: { $0[keyPath: sectionKeyPath] }, database: database, scheduler: scheduler ) } -} -extension FetchAll { - public init< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil - ) - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database) - return - } - self.init( - wrappedValue: wrappedValue, - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: nil - ) - } - - public init< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table + @discardableResult + public func load< + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable - ) + ) async throws -> FetchSubscription where Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) - return + guard let sectioning = sectioning(From.columns) else { + return try await load(statement, database: database, scheduler: scheduler) } - self.init( - wrappedValue: wrappedValue, + return try await loadSections( request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), sectionBy: sectioning, database: database, @@ -1014,29 +803,25 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable ) async throws -> FetchSubscription where Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - return try await load(statement, database: database) + guard let sectioning = sectioning(From.columns, J.columns) else { + return try await load(statement, database: database, scheduler: scheduler) } return try await loadSections( request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), sectionBy: sectioning, database: database, - scheduler: nil + scheduler: scheduler ) } @@ -1072,19 +857,58 @@ extension FetchAll { #if canImport(SwiftUI) extension FetchAll { + /// Initializes this property with a query that fetches every row from a table, grouping + /// results into sections. + /// + /// - Parameters: + /// - wrappedValue: A default collection to associate with this property. + /// - sectioning: A closure that returns a string expression, or an ordering of one, to + /// group results by, or `nil` for no grouping. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - animation: The animation to use for user interface changes that result from changes to + /// the fetched results. @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table - >( + public init( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + @_SectionBuilder sectionBy sectioning: (Element.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where Element: StructuredQueriesCore.Table, Element.QueryOutput == Element { + self.init( + wrappedValue: wrappedValue, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } + + /// Initializes this property with a query associated with the wrapped value, grouping results + /// into sections. + /// + /// - Parameters: + /// - wrappedValue: A default collection to associate with this property. + /// - statement: A query associated with the wrapped value. + /// - sectioning: A closure that returns a string expression, or an ordering of one, to + /// group results by, or `nil` for no grouping. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - animation: The animation to use for user interface changes that result from changes to + /// the fetched results. + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + public init( + wrappedValue: [Element] = [], + _ statement: S, + @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation ) where - Element == V.QueryOutput, - V.QueryOutput: Sendable + Element == S.From.QueryOutput, + S.QueryValue == (), + S.From.QueryOutput: Sendable, + S.Joins == () { self.init( wrappedValue: wrappedValue, @@ -1095,35 +919,77 @@ extension FetchAll { ) } + /// Initializes this property with a query that fetches every row from a table, grouping + /// results into sections. + /// + /// - Parameters: + /// - wrappedValue: A default collection to associate with this property. + /// - sectionKeyPath: A key path to a string column to group results by. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - animation: The animation to use for user interface changes that result from changes to + /// the fetched results. + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + public init( + wrappedValue: [Element] = [], + sectionBy sectionKeyPath: KeyPath< + Element.TableColumns, some QueryExpression> + >, + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where Element: StructuredQueriesCore.Table, Element.QueryOutput == Element { + self.init( + wrappedValue: wrappedValue, + sectionBy: { $0[keyPath: sectionKeyPath] }, + database: database, + animation: animation + ) + } + + /// Initializes this property with a query associated with the wrapped value, grouping results + /// into sections. + /// + /// - Parameters: + /// - wrappedValue: A default collection to associate with this property. + /// - statement: A query associated with the wrapped value. + /// - sectionKeyPath: A key path to a string column to group results by. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - animation: The animation to use for user interface changes that result from changes to + /// the fetched results. @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @discardableResult - public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + public init( + wrappedValue: [Element] = [], + _ statement: S, + sectionBy sectionKeyPath: KeyPath< + S.From.TableColumns, some QueryExpression> + >, database: (any DatabaseReader)? = nil, - animation: Animation? - ) async throws -> FetchSubscription + animation: Animation + ) where - Element == V.QueryOutput, - V.QueryOutput: Sendable + Element == S.From.QueryOutput, + S.QueryValue == (), + S.From.QueryOutput: Sendable, + S.Joins == () { - try await load( + self.init( + wrappedValue: wrappedValue, statement, - sectionBy: sectioning, + sectionBy: { $0[keyPath: sectionKeyPath] }, database: database, - scheduler: .animation(animation) + animation: animation ) } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation ) @@ -1141,20 +1007,21 @@ extension FetchAll { } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @discardableResult - public func load< + public init< V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table >( + wrappedValue: [Element] = [], _ statement: Select, @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, - animation: Animation? - ) async throws -> FetchSubscription + animation: Animation + ) where Element == V.QueryOutput, V.QueryOutput: Sendable { - try await load( + self.init( + wrappedValue: wrappedValue, statement, sectionBy: sectioning, database: database, @@ -1190,6 +1057,117 @@ extension FetchAll { ) } + /// Replaces the wrapped value with data from the given query, grouping results into sections. + /// + /// - Parameters: + /// - statement: A query associated with the wrapped value. + /// - sectioning: A closure that returns a string expression, or an ordering of one, to + /// group results by, or `nil` for no grouping. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - animation: The animation to use for user interface changes that result from changes to + /// the fetched results. + /// - Returns: A subscription associated with the observation. + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @discardableResult + public func load( + _ statement: S, + @_SectionBuilder sectionBy sectioning: (S.From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation? + ) async throws -> FetchSubscription + where + Element == S.From.QueryOutput, + S.QueryValue == (), + S.From.QueryOutput: Sendable, + S.Joins == () + { + try await load( + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } + + /// Replaces the wrapped value with data from the given query, grouping results into sections. + /// + /// - Parameters: + /// - statement: A query associated with the wrapped value. + /// - sectionKeyPath: A key path to a string column to group results by. + /// - database: The database to read from. A value of `nil` will use the default database + /// (`@Dependency(\.defaultDatabase)`). + /// - animation: The animation to use for user interface changes that result from changes to + /// the fetched results. + /// - Returns: A subscription associated with the observation. + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @discardableResult + public func load( + _ statement: S, + sectionBy sectionKeyPath: KeyPath< + S.From.TableColumns, some QueryExpression> + >, + database: (any DatabaseReader)? = nil, + animation: Animation? + ) async throws -> FetchSubscription + where + Element == S.From.QueryOutput, + S.QueryValue == (), + S.From.QueryOutput: Sendable, + S.Joins == () + { + try await load( + statement, + sectionBy: { $0[keyPath: sectionKeyPath] }, + database: database, + animation: animation + ) + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @discardableResult + public func load< + V: QueryRepresentable, From: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation? + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + try await load( + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @discardableResult + public func load< + V: QueryRepresentable, From: StructuredQueriesCore.Table, J: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns, J.TableColumns) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation? + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + try await load( + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @discardableResult public func load< From 23041e5c74dd5cba18e5c3233092ab151c8e3c25 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Jul 2026 16:42:22 -0700 Subject: [PATCH 3/4] Clean up overloads --- Sources/SQLiteData/FetchAll+Sections.swift | 223 +++------------------ 1 file changed, 28 insertions(+), 195 deletions(-) diff --git a/Sources/SQLiteData/FetchAll+Sections.swift b/Sources/SQLiteData/FetchAll+Sections.swift index 1d78bd89..8c9ab6f7 100644 --- a/Sources/SQLiteData/FetchAll+Sections.swift +++ b/Sources/SQLiteData/FetchAll+Sections.swift @@ -183,18 +183,19 @@ extension FetchAll { } public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> + _Sectioning?, database: (any DatabaseReader)? = nil ) where Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns) else { + guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { self.init(wrappedValue: wrappedValue, statement, database: database) return } @@ -232,36 +233,6 @@ extension FetchAll { ) } - public init< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil - ) - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database) - return - } - self.init( - wrappedValue: wrappedValue, - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: nil - ) - } - /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -323,17 +294,18 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> + _Sectioning?, database: (any DatabaseReader)? = nil ) async throws -> FetchSubscription where Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns) else { + guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { return try await load(statement, database: database) } return try await loadSections( @@ -367,34 +339,6 @@ extension FetchAll { ) } - @discardableResult - public func load< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil - ) async throws -> FetchSubscription - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - return try await load(statement, database: database) - } - return try await loadSections( - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: nil - ) - } - fileprivate init( wrappedValue: [Element], statement: Select<(), From, ()>, @@ -629,11 +573,12 @@ extension FetchAll { } public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> + _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) @@ -641,7 +586,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns) else { + guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) return } @@ -680,37 +625,6 @@ extension FetchAll { ) } - public init< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable - ) - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) - return - } - self.init( - wrappedValue: wrappedValue, - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: scheduler - ) - } - /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -779,10 +693,11 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> + _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) async throws -> FetchSubscription @@ -790,7 +705,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning(From.columns) else { + guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { return try await load(statement, database: database, scheduler: scheduler) } return try await loadSections( @@ -824,35 +739,6 @@ extension FetchAll { scheduler: scheduler ) } - - @discardableResult - public func load< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler & Hashable - ) async throws -> FetchSubscription - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { - return try await load(statement, database: database, scheduler: scheduler) - } - return try await loadSections( - request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), - sectionBy: sectioning, - database: database, - scheduler: scheduler - ) - } } #if canImport(SwiftUI) @@ -985,11 +871,12 @@ extension FetchAll { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> + _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation ) @@ -1029,34 +916,6 @@ extension FetchAll { ) } - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - public init< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - animation: Animation - ) - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - self.init( - wrappedValue: wrappedValue, - statement, - sectionBy: sectioning, - database: database, - scheduler: .animation(animation) - ) - } - /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -1127,10 +986,11 @@ extension FetchAll { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> + _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation? ) async throws -> FetchSubscription @@ -1167,33 +1027,6 @@ extension FetchAll { scheduler: .animation(animation) ) } - - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - @discardableResult - public func load< - V: QueryRepresentable, - From: StructuredQueriesCore.Table, - J1: StructuredQueriesCore.Table, - each J2: StructuredQueriesCore.Table - >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ( - From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns - ) -> _Sectioning?, - database: (any DatabaseReader)? = nil, - animation: Animation? - ) async throws -> FetchSubscription - where - Element == V.QueryOutput, - V.QueryOutput: Sendable - { - try await load( - statement, - sectionBy: sectioning, - database: database, - scheduler: .animation(animation) - ) - } } #endif From a9a176d75ce6d9c886bd21e9bbae9cdda27c0d86 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Jul 2026 17:11:39 -0700 Subject: [PATCH 4/4] revert --- Sources/SQLiteData/FetchAll+Sections.swift | 223 ++++++++++++++++++--- 1 file changed, 195 insertions(+), 28 deletions(-) diff --git a/Sources/SQLiteData/FetchAll+Sections.swift b/Sources/SQLiteData/FetchAll+Sections.swift index 8c9ab6f7..1d78bd89 100644 --- a/Sources/SQLiteData/FetchAll+Sections.swift +++ b/Sources/SQLiteData/FetchAll+Sections.swift @@ -183,19 +183,18 @@ extension FetchAll { } public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> - _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil ) where Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { + guard let sectioning = sectioning(From.columns) else { self.init(wrappedValue: wrappedValue, statement, database: database) return } @@ -233,6 +232,36 @@ extension FetchAll { ) } + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database) + return + } + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -294,18 +323,17 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> - _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil ) async throws -> FetchSubscription where Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { + guard let sectioning = sectioning(From.columns) else { return try await load(statement, database: database) } return try await loadSections( @@ -339,6 +367,34 @@ extension FetchAll { ) } + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + return try await load(statement, database: database) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: nil + ) + } + fileprivate init( wrappedValue: [Element], statement: Select<(), From, ()>, @@ -573,12 +629,11 @@ extension FetchAll { } public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> - _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) @@ -586,7 +641,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { + guard let sectioning = sectioning(From.columns) else { self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) return } @@ -625,6 +680,37 @@ extension FetchAll { ) } + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + self.init(wrappedValue: wrappedValue, statement, database: database, scheduler: scheduler) + return + } + self.init( + wrappedValue: wrappedValue, + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: scheduler + ) + } + /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -693,11 +779,10 @@ extension FetchAll { @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> - _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, scheduler: some ValueObservationScheduler & Hashable ) async throws -> FetchSubscription @@ -705,7 +790,7 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - guard let sectioning = sectioning((From.columns, repeat (each J).columns)) else { + guard let sectioning = sectioning(From.columns) else { return try await load(statement, database: database, scheduler: scheduler) } return try await loadSections( @@ -739,6 +824,35 @@ extension FetchAll { scheduler: scheduler ) } + + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + guard let sectioning = sectioning(From.columns, J1.columns, repeat (each J2).columns) else { + return try await load(statement, database: database, scheduler: scheduler) + } + return try await loadSections( + request: FetchAllSectionedStatementValueRequest(statement: statement, sectionBy: sectioning), + sectionBy: sectioning, + database: database, + scheduler: scheduler + ) + } } #if canImport(SwiftUI) @@ -871,12 +985,11 @@ extension FetchAll { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) public init< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( wrappedValue: [Element] = [], - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> - _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation ) @@ -916,6 +1029,34 @@ extension FetchAll { ) } + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + public init< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + wrappedValue: [Element] = [], + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + self.init( + wrappedValue: wrappedValue, + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } + /// Replaces the wrapped value with data from the given query, grouping results into sections. /// /// - Parameters: @@ -986,11 +1127,10 @@ extension FetchAll { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @discardableResult public func load< - V: QueryRepresentable, From: StructuredQueriesCore.Table, each J: StructuredQueriesCore.Table + V: QueryRepresentable, From: StructuredQueriesCore.Table >( - _ statement: Select, - @_SectionBuilder sectionBy sectioning: ((From.TableColumns, repeat (each J).TableColumns)) -> - _Sectioning?, + _ statement: Select, + @_SectionBuilder sectionBy sectioning: (From.TableColumns) -> _Sectioning?, database: (any DatabaseReader)? = nil, animation: Animation? ) async throws -> FetchSubscription @@ -1027,6 +1167,33 @@ extension FetchAll { scheduler: .animation(animation) ) } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @discardableResult + public func load< + V: QueryRepresentable, + From: StructuredQueriesCore.Table, + J1: StructuredQueriesCore.Table, + each J2: StructuredQueriesCore.Table + >( + _ statement: Select, + @_SectionBuilder sectionBy sectioning: ( + From.TableColumns, J1.TableColumns, repeat (each J2).TableColumns + ) -> _Sectioning?, + database: (any DatabaseReader)? = nil, + animation: Animation? + ) async throws -> FetchSubscription + where + Element == V.QueryOutput, + V.QueryOutput: Sendable + { + try await load( + statement, + sectionBy: sectioning, + database: database, + scheduler: .animation(animation) + ) + } } #endif