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
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>
private let state: SwiftUI.State<FetchBox<Value>>
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
97 changes: 70 additions & 27 deletions Sources/SQLiteData/FetchAll.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
public import GRDB
public import Sharing
public import StructuredQueriesCore
public import Sharing

#if canImport(Combine)
public import Combine
Expand All @@ -22,11 +21,32 @@ public import Sharing
@dynamicMemberLookup
@propertyWrapper
public struct FetchAll<Element: 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<[Element]> = 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<[Element]> {
@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<[Element]>
private let state: SwiftUI.State<FetchBox<[Element]>>
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<[Element]> = SharedReader(value: [])
#endif

/// A collection of data associated with the underlying query.
public var wrappedValue: [Element] {
Expand Down Expand Up @@ -139,13 +159,12 @@ public struct FetchAll<Element: Sendable>: Sendable {
Element == V.QueryOutput,
V.QueryOutput: Sendable
{
let request = FetchAllStatementValueRequest(statement: statement)
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(
FetchAllStatementValueRequest(statement: statement),
database: database
)
.fetch(request, database: database)
)
setFetchKeyID(for: request, database: database, scheduler: nil)
}

/// Initializes this property with a query associated with the wrapped value.
Expand All @@ -164,13 +183,12 @@ public struct FetchAll<Element: Sendable>: Sendable {
Element: QueryRepresentable,
Element == S.QueryValue.QueryOutput
{
let request = FetchAllStatementValueRequest(statement: statement)
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(
FetchAllStatementValueRequest(statement: statement),
database: database
)
.fetch(request, database: database)
)
setFetchKeyID(for: request, database: database, scheduler: nil)
}

/// Replaces the wrapped value with data from the given query.
Expand Down Expand Up @@ -219,10 +237,29 @@ public struct FetchAll<Element: Sendable>: Sendable {
)
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 FetchAll {
@available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.")
@available(
*,
deprecated,
message: """
'@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'.
"""
)
public init(
wrappedValue: [Element] = [],
database: (any DatabaseReader)? = nil,
Expand Down Expand Up @@ -294,14 +331,12 @@ extension FetchAll {
Element == V.QueryOutput,
V.QueryOutput: Sendable
{
let request = FetchAllStatementValueRequest(statement: statement)
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(
FetchAllStatementValueRequest(statement: statement),
database: database,
scheduler: scheduler
)
.fetch(request, database: database, scheduler: scheduler)
)
setFetchKeyID(for: request, database: database, scheduler: scheduler)
}

/// Initializes this property with a query associated with the wrapped value.
Expand All @@ -323,14 +358,12 @@ extension FetchAll {
Element: QueryRepresentable,
Element == S.QueryValue.QueryOutput
{
let request = FetchAllStatementValueRequest(statement: statement)
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(
FetchAllStatementValueRequest(statement: statement),
database: database,
scheduler: scheduler
)
.fetch(request, database: database, scheduler: scheduler)
)
setFetchKeyID(for: request, database: database, scheduler: scheduler)
}

/// Replaces the wrapped value with data from the given query.
Expand Down Expand Up @@ -403,10 +436,20 @@ extension FetchAll: Equatable where Element: Equatable {
#if canImport(SwiftUI)
extension FetchAll: DynamicProperty {
public func update() {
sharedReader.update()
let persisted = state.wrappedValue
if persisted !== box {
persisted.reconcile(from: box, propertyName: "@FetchAll")
}
persisted.subscribe(generation: generation)
}

@available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.")
@available(
*,
deprecated,
message: """
'@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'.
"""
)
public init(
wrappedValue: [Element] = [],
database: (any DatabaseReader)? = nil,
Expand Down
Loading
Loading