From 8804e168ea6a20ac79916f05c5f7b724cc2193c7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Feb 2025 11:54:29 -0800 Subject: [PATCH 1/3] Include animation identity in fetch key --- Package.resolved | 16 +- .../Articles/ComparisonWithSwiftData.md | 2 +- .../Documentation.docc/Articles/Fetching.md | 12 +- .../Documentation.docc/Extensions/Fetch.md | 9 +- .../Documentation.docc/Extensions/FetchAll.md | 6 +- .../Documentation.docc/Extensions/FetchOne.md | 6 +- .../Documentation.docc/SharingGRDB.md | 6 +- Sources/SharingGRDB/FetchKey+SwiftUI.swift | 32 ++-- Sources/SharingGRDB/FetchKey.swift | 172 +++++++++++++----- Tests/SharingGRDBTests/SharingGRDBTests.swift | 7 + 10 files changed, 188 insertions(+), 80 deletions(-) diff --git a/Package.resolved b/Package.resolved index 63f72008..52e8db32 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift", "state" : { - "revision" : "c7205b172f38439e7ee62c208d1d76fa353c0a81", - "version" : "7.2.0" + "revision" : "6eba24d16952452a8a54f6a639491f3c8215527f", + "version" : "7.3.0" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "52b5e1a09dc016e64ce253e19ab3124b7fae9ac9", - "version" : "1.7.0" + "revision" : "121a428c505c01c4ce02d5ada1c8fc3da93afce9", + "version" : "1.8.0" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "10ba53dd428aed9fc4a1543d3271860a6d4b8dd2", - "version" : "2.3.0" + "revision" : "2c840cf2ae0526ad6090e7796c4e13d9a2339f4a", + "version" : "2.3.3" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "b444594f79844b0d6d76d70fbfb3f7f71728f938", - "version" : "1.5.1" + "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", + "version" : "1.5.2" } } ], diff --git a/Sources/SharingGRDB/Documentation.docc/Articles/ComparisonWithSwiftData.md b/Sources/SharingGRDB/Documentation.docc/Articles/ComparisonWithSwiftData.md index c4208974..ca19f816 100644 --- a/Sources/SharingGRDB/Documentation.docc/Articles/ComparisonWithSwiftData.md +++ b/Sources/SharingGRDB/Documentation.docc/Articles/ComparisonWithSwiftData.md @@ -110,7 +110,7 @@ whereas you use the `@Query` macro with SwiftData: The `@SharedReader` property wrapper takes a variety of options, detailed more in , and allows you to write raw SQL queries for fetching and aggregating data from your database. It is also possibly to construct SQL queries using GRDB's query builder syntax. See -[`fetch`]() for more information. +[`fetch`]() for more information. ### Fetching data for an @Observable model diff --git a/Sources/SharingGRDB/Documentation.docc/Articles/Fetching.md b/Sources/SharingGRDB/Documentation.docc/Articles/Fetching.md index 2508067a..c6856cac 100644 --- a/Sources/SharingGRDB/Documentation.docc/Articles/Fetching.md +++ b/Sources/SharingGRDB/Documentation.docc/Articles/Fetching.md @@ -5,9 +5,9 @@ Learn about the various tools for fetching data from a SQLite database. ## Overview All data fetching happens by providing -[`fetchAll`](), -[`fetchOne`](), or - [`fetch`](), to the `@SharedReader` +[`fetchAll`](), +[`fetchOne`](), or + [`fetch`](), to the `@SharedReader` property wrapper. The primary differences between these choices is whether you want to specify your query as a raw SQL string, or as a query built with GRDB's query building tools. @@ -20,7 +20,7 @@ query as a raw SQL string, or as a query built with GRDB's query building tools. For simple queries it can often be very convenient to specify how you want to fetch data from SQLite as a raw SQL query string. For example, if you simply want to fetch all records from a table, you can do so using the -[`fetchAll`]() key: +[`fetchAll`]() key: ```swift @SharedReader(.fetchAll(sql: "SELECT * FROM items")) var items: [Item] @@ -35,7 +35,7 @@ var items: [Item] Or, if you want to only compute an aggregate of the data in a table, such as the count of the rows, you can do so using the -[`fetchOne`]() key: +[`fetchOne`]() key: ```swift @SharedReader(.fetchOne(sql: "SELECT count(*) FROM items")) @@ -142,7 +142,7 @@ struct Items: FetchKeyRequest { ``` With this conformance defined one can use -[`fetch`]() key to execute the +[`fetch`]() key to execute the query specified by the `Items` type: ```swift diff --git a/Sources/SharingGRDB/Documentation.docc/Extensions/Fetch.md b/Sources/SharingGRDB/Documentation.docc/Extensions/Fetch.md index 4ffe5c57..75631699 100644 --- a/Sources/SharingGRDB/Documentation.docc/Extensions/Fetch.md +++ b/Sources/SharingGRDB/Documentation.docc/Extensions/Fetch.md @@ -1,4 +1,4 @@ -# ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` +# ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` ## Overview @@ -10,13 +10,18 @@ ### Collections -- ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8m3f7`` +- ``Sharing/SharedReaderKey/fetch(_:database:)-1ee8v`` ### SwiftUI integration - ``Sharing/SharedReaderKey/fetch(_:database:animation:)-rgj4`` - ``Sharing/SharedReaderKey/fetch(_:database:animation:)-j9jb`` +### Custom scheduling + +- ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-9arcp`` +- ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-53u9o`` + ### Sharing infrastructure - ``FetchKey`` diff --git a/Sources/SharingGRDB/Documentation.docc/Extensions/FetchAll.md b/Sources/SharingGRDB/Documentation.docc/Extensions/FetchAll.md index 599c44a3..93aa47da 100644 --- a/Sources/SharingGRDB/Documentation.docc/Extensions/FetchAll.md +++ b/Sources/SharingGRDB/Documentation.docc/Extensions/FetchAll.md @@ -1,4 +1,4 @@ -# ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` +# ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` ## Overview @@ -7,3 +7,7 @@ ### SwiftUI integration - ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:animation:)`` + +### Custom scheduling + +- ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` diff --git a/Sources/SharingGRDB/Documentation.docc/Extensions/FetchOne.md b/Sources/SharingGRDB/Documentation.docc/Extensions/FetchOne.md index 73905d1a..e20a0b6d 100644 --- a/Sources/SharingGRDB/Documentation.docc/Extensions/FetchOne.md +++ b/Sources/SharingGRDB/Documentation.docc/Extensions/FetchOne.md @@ -1,4 +1,4 @@ -# ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:scheduler:)`` +# ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:)`` ## Overview @@ -7,3 +7,7 @@ ### SwiftUI integration - ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:animation:)`` + +### Custom scheduling + +- ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:scheduler:)`` diff --git a/Sources/SharingGRDB/Documentation.docc/SharingGRDB.md b/Sources/SharingGRDB/Documentation.docc/SharingGRDB.md index 7af4f987..aaa717dc 100644 --- a/Sources/SharingGRDB/Documentation.docc/SharingGRDB.md +++ b/Sources/SharingGRDB/Documentation.docc/SharingGRDB.md @@ -179,6 +179,6 @@ with SQLite to take full advantage of GRDB and SharingGRDB. ### Fetch strategies -- ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` -- ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:scheduler:)`` -- ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` +- ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` +- ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:)`` +- ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` diff --git a/Sources/SharingGRDB/FetchKey+SwiftUI.swift b/Sources/SharingGRDB/FetchKey+SwiftUI.swift index 7988bc88..1911cd74 100644 --- a/Sources/SharingGRDB/FetchKey+SwiftUI.swift +++ b/Sources/SharingGRDB/FetchKey+SwiftUI.swift @@ -6,10 +6,9 @@ extension SharedReaderKey { /// A key that can query for data in a SQLite database. /// - /// A version of ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` that can be - /// configured with a SwiftUI animation. See - /// ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` for more info on how to - /// use this API. + /// A version of ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` that can be configured + /// with a SwiftUI animation. See ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` for more + /// info on how to use this API. /// /// - Parameters: /// - request: A request describing the data to fetch. @@ -28,10 +27,9 @@ /// A key that can query for a collection of data in a SQLite database. /// - /// A version of ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` that can be - /// configured with a SwiftUI animation. See - /// ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` for more info on how to - /// use this API. + /// A version of ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` that can be configured + /// with a SwiftUI animation. See ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` for more + /// info on how to use this API. /// /// - Parameters: /// - request: A request describing the data to fetch. @@ -50,10 +48,10 @@ /// A key that can query for a collection of data in a SQLite database. /// - /// A version of ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` that - /// can be configured with a SwiftUI animation. See - /// ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` for more information - /// on how to use this API. + /// A version of ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` that can be + /// configured with a SwiftUI animation. See + /// ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` for more information on how to + /// use this API. /// /// - Parameters: /// - sql: A raw SQL string describing the data to fetch. @@ -79,10 +77,10 @@ /// A key that can query for a value in a SQLite database. /// - /// A version of ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:scheduler:)`` that - /// can be configured with a SwiftUI animation. See - /// ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` for more information - /// on how to use this API. + /// A version of ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:)`` that can be + /// configured with a SwiftUI animation. See + /// ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` for more information on how to + /// use this API. /// /// - Parameters: /// - sql: A raw SQL string describing the data to fetch. @@ -107,7 +105,7 @@ } } - private struct AnimatedScheduler: ValueObservationScheduler { + private struct AnimatedScheduler: ValueObservationScheduler, Hashable { let animation: Animation func immediateInitialValue() -> Bool { true } func schedule(_ action: @escaping @Sendable () -> Void) { diff --git a/Sources/SharingGRDB/FetchKey.swift b/Sources/SharingGRDB/FetchKey.swift index 7ae6222b..37963521 100644 --- a/Sources/SharingGRDB/FetchKey.swift +++ b/Sources/SharingGRDB/FetchKey.swift @@ -32,53 +32,49 @@ extension SharedReaderKey { /// ``` /// /// For simpler querying needs, you can skip the ceremony of defining a ``FetchKeyRequest`` and - /// use a raw SQL query with - /// ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:scheduler:)`` or - /// ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:scheduler:)``, instead. + /// use a raw SQL query with ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` or + /// ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:)``, instead. + /// + /// To animate or observe changes with a custom scheduler, see + /// ``Sharing/SharedReaderKey/fetch(_:database:animation:)-rgj4`` or + /// ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-9arcp``. /// /// - Parameters: /// - request: A request describing the data to fetch. /// - database: The database to read from. A value of `nil` will use the /// ``Dependencies/DependencyValues/defaultDatabase``. - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. public static func fetch( _ request: some FetchKeyRequest, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler = .async(onQueue: .main) + database: (any DatabaseReader)? = nil ) -> Self where Self == FetchKey { - FetchKey(request: request, database: database, scheduler: scheduler) + FetchKey(request: request, database: database, scheduler: nil) } /// A key that can query for a collection of data in a SQLite database. /// - /// A version of ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` that allows you to - /// omit the type and default from the `@SharedReader` property wrapper: + /// A version of ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` that allows you to omit the + /// type and default from the `@SharedReader` property wrapper: /// /// ```diff /// -@SharedReader(.fetch(Items()) var items: [Item] = [] /// +@SharedReader(.fetch(Items()) var items /// ``` /// - /// See ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig`` for more info on how to - /// use this API. + /// See ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` for more info on how to use this API. /// /// - Parameters: /// - request: A request describing the data to fetch. /// - database: The database to read from. A value of `nil` will use the /// ``Dependencies/DependencyValues/defaultDatabase``. - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. public static func fetch( _ request: some FetchKeyRequest, - database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler = .async(onQueue: .main) + database: (any DatabaseReader)? = nil ) -> Self where Self == FetchKey.Default { - Self[.fetch(request, database: database, scheduler: scheduler), default: Value()] + Self[.fetch(request, database: database), default: Value()] } /// A key that can query for a collection of data in a SQLite database. @@ -90,26 +86,22 @@ extension SharedReaderKey { /// @SharedReader(.fetchAll(sql: "SELECT * FROM items")) var items: [Item] /// ``` /// - /// For more complex querying needs, see - /// ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig``. + /// For more complex querying needs, see ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd``. /// /// - Parameters: /// - sql: A raw SQL string describing the data to fetch. /// - arguments: Arguments to bind to the SQL statement. /// - database: The database to read from. A value of `nil` will use the /// ``Dependencies/DependencyValues/defaultDatabase``. - /// - scheduler: The scheduler to observe from. By default, database observation is performed - /// asynchronously on the main queue. /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. public static func fetchAll( sql: String, arguments: StatementArguments = StatementArguments(), database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler = .async(onQueue: .main) ) -> Self where Self == FetchKey<[Record]>.Default { Self[ - .fetch(FetchAll(sql: sql, arguments: arguments), database: database, scheduler: scheduler), + .fetch(FetchAll(sql: sql, arguments: arguments), database: database), default: [] ] } @@ -123,8 +115,101 @@ extension SharedReaderKey { /// @SharedReader(.fetchOne(sql: "SELECT count(*) FROM items")) var itemsCount = 0 /// ``` /// - /// For more complex querying needs, see - /// ``Sharing/SharedReaderKey/fetch(_:database:scheduler:)-8kkig``. + /// For more complex querying needs, see ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd``. + /// + /// - Parameters: + /// - sql: A raw SQL string describing the data to fetch. + /// - arguments: Arguments to bind to the SQL statement. + /// - database: The database to read from. A value of `nil` will use the + /// ``Dependencies/DependencyValues/defaultDatabase``. + /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. + public static func fetchOne( + sql: String, + arguments: StatementArguments = StatementArguments(), + database: (any DatabaseReader)? = nil, + ) -> Self + where Self == FetchKey { + .fetch(FetchOne(sql: sql, arguments: arguments), database: database) + } +} + +extension SharedReaderKey { + /// A key that can query for data in a SQLite database. + /// + /// A version of ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` that can be configured + /// with a scheduler. See ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` for more info on + /// how to use this API. + /// + /// - Parameters: + /// - request: A request describing the data to fetch. + /// - database: The database to read from. A value of `nil` will use the + /// ``Dependencies/DependencyValues/defaultDatabase``. + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. + /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. + public static func fetch( + _ request: some FetchKeyRequest, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) -> Self + where Self == FetchKey { + FetchKey(request: request, database: database, scheduler: scheduler) + } + + /// A key that can query for a collection of data in a SQLite database. + /// + /// A version of ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` that can be configured + /// with a scheduler. See ``Sharing/SharedReaderKey/fetch(_:database:)-3qcpd`` for more info on + /// how to use this API. + /// + /// - Parameters: + /// - request: A request describing the data to fetch. + /// - database: The database to read from. A value of `nil` will use the + /// ``Dependencies/DependencyValues/defaultDatabase``. + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. + /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. + public static func fetch( + _ request: some FetchKeyRequest, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) -> Self + where Self == FetchKey.Default { + Self[.fetch(request, database: database, scheduler: scheduler), default: Value()] + } + + /// A key that can query for a collection of data in a SQLite database. + /// + /// A version of ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` that can be + /// configured with a scheduler. See ``Sharing/SharedReaderKey/fetchAll(sql:arguments:database:)`` + /// for more info on how to use this API. + /// + /// - Parameters: + /// - sql: A raw SQL string describing the data to fetch. + /// - arguments: Arguments to bind to the SQL statement. + /// - database: The database to read from. A value of `nil` will use the + /// ``Dependencies/DependencyValues/defaultDatabase``. + /// - scheduler: The scheduler to observe from. By default, database observation is performed + /// asynchronously on the main queue. + /// - Returns: A key that can be passed to the `@SharedReader` property wrapper. + public static func fetchAll( + sql: String, + arguments: StatementArguments = StatementArguments(), + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) -> Self + where Self == FetchKey<[Record]>.Default { + Self[ + .fetch(FetchAll(sql: sql, arguments: arguments), database: database, scheduler: scheduler), + default: [] + ] + } + + /// A key that can query for a value in a SQLite database. + /// + /// A version of ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:)`` that can be + /// configured with a scheduler. See ``Sharing/SharedReaderKey/fetchOne(sql:arguments:database:)`` + /// for more info on how to use this API. /// /// - Parameters: /// - sql: A raw SQL string describing the data to fetch. @@ -138,7 +223,7 @@ extension SharedReaderKey { sql: String, arguments: StatementArguments = StatementArguments(), database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler = .async(onQueue: .main) + scheduler: some ValueObservationScheduler & Hashable ) -> Self where Self == FetchKey { .fetch(FetchOne(sql: sql, arguments: arguments), database: database, scheduler: scheduler) @@ -148,14 +233,14 @@ extension SharedReaderKey { /// A type defining a reader of GRDB queries. /// /// You typically do not refer to this type directly, and will use -/// [`fetchAll`](), -/// [`fetchOne`](), and -/// [`fetch`]() to create instances, +/// [`fetchAll`](), +/// [`fetchOne`](), and +/// [`fetch`]() to create instances, /// instead. public struct FetchKey: SharedReaderKey { let database: any DatabaseReader let request: any FetchKeyRequest - let scheduler: any ValueObservationScheduler + let scheduler: (any ValueObservationScheduler & Hashable)? #if DEBUG let isDefaultDatabase: Bool #endif @@ -163,13 +248,13 @@ public struct FetchKey: SharedReaderKey { public typealias ID = FetchKeyID public var id: ID { - ID(database: database, request: request) + ID(database: database, request: request, scheduler: scheduler) } init( request: some FetchKeyRequest, database: (any DatabaseReader)? = nil, - scheduler: some ValueObservationScheduler = .async(onQueue: .main) + scheduler: (any ValueObservationScheduler & Hashable)? ) { @Dependency(\.defaultDatabase) var defaultDatabase self.scheduler = scheduler @@ -195,6 +280,7 @@ public struct FetchKey: SharedReaderKey { continuation.resume(with: Result { try database.read(request.fetch) }) return } + let scheduler: any ValueObservationScheduler = scheduler ?? .async(onQueue: .main) database.asyncRead { dbResult in let result = dbResult.flatMap { db in Result { @@ -225,6 +311,7 @@ public struct FetchKey: SharedReaderKey { let observation = ValueObservation.tracking { db in Result { try request.fetch(db) } } + let scheduler: any ValueObservationScheduler = scheduler ?? .async(onQueue: .main) #if canImport(Combine) let dropFirst = switch context { @@ -257,7 +344,14 @@ public struct FetchKey: SharedReaderKey { let cancellable = observation.start(in: database, scheduling: scheduler) { error in subscriber.yield(throwing: error) } onChange: { newValue in - subscriber.yield(newValue) + switch newValue { + case let .success(value): + subscriber.yield(value) + case let .failure(error) where error is NotFound: + subscriber.yieldReturningInitialValue() + case let .failure(error): + subscriber.yield(throwing: error) + } } return SharedSubscription { cancellable.cancel() @@ -271,21 +365,17 @@ public struct FetchKeyID: Hashable { fileprivate let databaseID: ObjectIdentifier fileprivate let request: AnyHashableSendable fileprivate let requestTypeID: ObjectIdentifier + fileprivate let scheduler: AnyHashableSendable? fileprivate init( database: any DatabaseReader, - request: some FetchKeyRequest + request: some FetchKeyRequest, + scheduler: (any ValueObservationScheduler & Hashable)? ) { self.databaseID = ObjectIdentifier(database) self.request = AnyHashableSendable(request) self.requestTypeID = ObjectIdentifier(type(of: request)) - - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(databaseID) - hasher.combine(request) - hasher.combine(requestTypeID) + self.scheduler = scheduler.map { AnyHashableSendable($0) } } } diff --git a/Tests/SharingGRDBTests/SharingGRDBTests.swift b/Tests/SharingGRDBTests/SharingGRDBTests.swift index d3c91b91..5ee839c7 100644 --- a/Tests/SharingGRDBTests/SharingGRDBTests.swift +++ b/Tests/SharingGRDBTests/SharingGRDBTests.swift @@ -74,6 +74,13 @@ import Testing let fetchKey2: some SharedReaderKey = .fetch(Fetch2()) #expect(fetchKey1.id.hashValue != fetchKey2.id.hashValue) } + + @Test(.dependency(\.defaultDatabase, try .database)) + func fetchAnimationHashValue() async throws { + let fetchKey1: some SharedReaderKey = .fetch(Fetch1()) + let fetchKey2: some SharedReaderKey = .fetch(Fetch2(), animation: .default) + #expect(fetchKey1.id.hashValue != fetchKey2.id.hashValue) + } } fileprivate struct Fetch1: FetchKeyRequest { From 2ebe107ce6dbe254ab8f1c4437266e773348235d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Feb 2025 11:59:12 -0800 Subject: [PATCH 2/3] wip --- Sources/SharingGRDB/FetchKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SharingGRDB/FetchKey.swift b/Sources/SharingGRDB/FetchKey.swift index 37963521..91bb4b3d 100644 --- a/Sources/SharingGRDB/FetchKey.swift +++ b/Sources/SharingGRDB/FetchKey.swift @@ -126,7 +126,7 @@ extension SharedReaderKey { public static func fetchOne( sql: String, arguments: StatementArguments = StatementArguments(), - database: (any DatabaseReader)? = nil, + database: (any DatabaseReader)? = nil ) -> Self where Self == FetchKey { .fetch(FetchOne(sql: sql, arguments: arguments), database: database) From b60cfe0ba7a96b0c7dab11218fe51251e387f95a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 27 Feb 2025 12:07:12 -0800 Subject: [PATCH 3/3] wip --- Sources/SharingGRDB/FetchKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SharingGRDB/FetchKey.swift b/Sources/SharingGRDB/FetchKey.swift index 91bb4b3d..e9ff58b4 100644 --- a/Sources/SharingGRDB/FetchKey.swift +++ b/Sources/SharingGRDB/FetchKey.swift @@ -97,7 +97,7 @@ extension SharedReaderKey { public static func fetchAll( sql: String, arguments: StatementArguments = StatementArguments(), - database: (any DatabaseReader)? = nil, + database: (any DatabaseReader)? = nil ) -> Self where Self == FetchKey<[Record]>.Default { Self[