Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Sources/SQLiteData/Documentation.docc/Articles/Fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,36 @@ This is a very efficient query that selects only the bare essentials of data tha
needs to do its job. This kind of query is a lot more cumbersome to perform in SwiftData because
you must construct a dedicated `FetchDescriptor` value and set its `propertiesToFetch`.

It is also possible to group the results of a query into sections by providing a `sectionBy:` key
path, similar to SwiftData's `@Query(sectionBy:)`. For example, given a `Reminder` table with a
string `category` column:

```swift
@FetchAll(Reminder.order(by: \.category), sectionBy: \.category)
var reminders
```

The flat results are still available from the property itself, and the projected value's
``FetchAll/sections`` property groups them into a ``ResultsSectionCollection``, which is perfect
for driving a sectioned list:

```swift
List {
ForEach($reminders.sections) { section in
Section(section.name) {
ForEach(section) { reminder in
Text(reminder.title)
}
}
}
}
```

Results are grouped into a section for each distinct value at the key path. Sections are ordered
by the position of their first element in the query's results, and elements within a section
follow the query's order, so you can control the order of sections by ordering the query by the
sectioned column.

[sq-safe-sql-strings]: https://swiftpackageindex.com/pointfreeco/swift-structured-queries/~/documentation/structuredqueriescore/safesqlstrings
[structured-queries-gh]: https://github.com/pointfreeco/swift-structured-queries
[structured-queries-docs]: https://swiftpackageindex.com/pointfreeco/swift-structured-queries/main/documentation/structuredqueriescore/
Expand Down
15 changes: 15 additions & 0 deletions Sources/SQLiteData/Documentation.docc/Extensions/FetchAll.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
- ``init(wrappedValue:_:database:)``
- ``load(_:database:)``

### Sectioning data

- ``init(wrappedValue:sectionBy:database:)``
- ``init(wrappedValue:_:sectionBy:database:)``
- ``load(_:sectionBy:database:)``
- ``sections``
- ``ResultsSectionCollection``
- ``ResultsSection``

### Accessing state

- ``wrappedValue``
Expand All @@ -21,7 +30,10 @@

- ``init(wrappedValue:database:animation:)``
- ``init(wrappedValue:_:database:animation:)``
- ``init(wrappedValue:sectionBy:database:animation:)``
- ``init(wrappedValue:_:sectionBy:database:animation:)``
- ``load(_:database:animation:)``
- ``load(_:sectionBy:database:animation:)``

### Combine integration

Expand All @@ -31,7 +43,10 @@

- ``init(wrappedValue:database:scheduler:)``
- ``init(wrappedValue:_:database:scheduler:)``
- ``init(wrappedValue:sectionBy:database:scheduler:)``
- ``init(wrappedValue:_:sectionBy:database:scheduler:)``
- ``load(_:database:scheduler:)``
- ``load(_:sectionBy:database:scheduler:)``

### Sharing infrastructure

Expand Down
54 changes: 47 additions & 7 deletions Sources/SQLiteData/Fetch.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
public import GRDB
public import Sharing
import StructuredQueriesCore
public import Sharing

#if canImport(Combine)
public import Combine
Expand All @@ -22,11 +21,32 @@ public import Sharing
@dynamicMemberLookup
@propertyWrapper
public struct Fetch<Value: Sendable>: Sendable {
/// The underlying shared reader powering the property wrapper.
///
/// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package,
/// a general solution to observing and persisting changes to external data sources.
public var sharedReader: SharedReader<Value>
#if canImport(SwiftUI)
/// The underlying shared reader powering the property wrapper.
///
/// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing)
/// package, a general solution to observing and persisting changes to external data sources.
public private(set) var sharedReader: SharedReader<Value> {
@storageRestrictions(initializes: box, state)
init(initialValue) {
let box = FetchBox(sharedReader: initialValue)
self.box = box
state = SwiftUI.State(wrappedValue: box)
}
get { state.wrappedValue.sharedReader }
nonmutating set { state.wrappedValue.sharedReader = newValue }
}

private let box: FetchBox<Value, Void>
private let state: SwiftUI.State<FetchBox<Value, Void>>
private let generation = SwiftUI.State(wrappedValue: 0)
#else
/// The underlying shared reader powering the property wrapper.
///
/// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing)
/// package, a general solution to observing and persisting changes to external data sources.
public private(set) var sharedReader: SharedReader<Value>
#endif

/// Data associated with the underlying query.
public var wrappedValue: Value {
Expand Down Expand Up @@ -93,6 +113,7 @@ public struct Fetch<Value: Sendable>: Sendable {
database: (any DatabaseReader)? = nil
) {
sharedReader = SharedReader(wrappedValue: wrappedValue, .fetch(request, database: database))
setFetchKeyID(for: request, database: database, scheduler: nil)
}

/// Replaces the wrapped value with data from the given request.
Expand All @@ -110,6 +131,19 @@ public struct Fetch<Value: Sendable>: Sendable {
try await sharedReader.load(.fetch(request, database: database))
return FetchSubscription(sharedReader: sharedReader)
}

#if !canImport(SwiftUI)
@_transparent
#endif
private func setFetchKeyID<V: Sendable>(
for request: some FetchKeyRequest<V>,
database: (any DatabaseReader)?,
scheduler: (any ValueObservationScheduler & Hashable)?
) {
#if canImport(SwiftUI)
box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id
#endif
}
}

extension Fetch {
Expand All @@ -132,6 +166,7 @@ extension Fetch {
wrappedValue: wrappedValue,
.fetch(request, database: database, scheduler: scheduler)
)
setFetchKeyID(for: request, database: database, scheduler: scheduler)
}

/// Replaces the wrapped value with data from the given request.
Expand Down Expand Up @@ -169,7 +204,11 @@ extension Fetch: Equatable where Value: Equatable {
#if canImport(SwiftUI)
extension Fetch: DynamicProperty {
public func update() {
sharedReader.update()
let persisted = state.wrappedValue
if persisted !== box {
persisted.reconcile(from: box, propertyName: "@Fetch")
}
persisted.subscribe(generation: generation)
}

/// Initializes this property with a request associated with the wrapped value.
Expand All @@ -192,6 +231,7 @@ extension Fetch: Equatable where Value: Equatable {
wrappedValue: wrappedValue,
.fetch(request, database: database, animation: animation)
)
setFetchKeyID(for: request, database: database, scheduler: .animation(animation))
}

/// Replaces the wrapped value with data from the given request.
Expand Down
Loading
Loading