diff --git a/Sources/CoreDataRepository/CoreDataRepository+Aggregate.swift b/Sources/CoreDataRepository/CoreDataRepository+Aggregate.swift index 77c4bc5..fa27ba5 100644 --- a/Sources/CoreDataRepository/CoreDataRepository+Aggregate.swift +++ b/Sources/CoreDataRepository/CoreDataRepository+Aggregate.swift @@ -90,9 +90,72 @@ extension CoreDataRepository { } /// Subscribe to the average of a managed object's numeric property for all instances that satisfy the predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func averageSubscription( + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncStream> { + AsyncStream { continuation in + let subscription = AggregateSubscription( + function: .average, + context: context.childContext(), + predicate: predicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the average of a managed object's numeric property for all instances that satisfy the predicate. + @inlinable + public func averageThrowingSubscription( + predicate: NSPredicate, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + let subscription = AggregateThrowingSubscription( + function: .average, + context: context.childContext(), + predicate: predicate, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the average of a managed object's numeric property for all instances that satisfy the predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. @inlinable public func averageThrowingSubscription( predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, entityDesc: NSEntityDescription, attributeDesc: NSAttributeDescription, groupBy: NSAttributeDescription? = nil, @@ -103,6 +166,7 @@ extension CoreDataRepository { function: .average, context: context.childContext(), predicate: predicate, + changeTrackingRequest: changeTrackingRequest, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy, @@ -160,9 +224,62 @@ extension CoreDataRepository { } /// Subscribe to the count or quantity of managed object instances that satisfy the predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func countSubscription( + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + as _: Value.Type + ) -> AsyncStream> { + AsyncStream { continuation in + let subscription = CountSubscription( + context: context.childContext(), + predicate: predicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: entityDesc, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the count or quantity of managed object instances that satisfy the predicate. + @inlinable + public func countThrowingSubscription( + predicate: NSPredicate, + entityDesc: NSEntityDescription, + as _: Value.Type + ) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + let subscription = CountThrowingSubscription( + context: context.childContext(), + predicate: predicate, + entityDesc: entityDesc, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the count or quantity of managed object instances that satisfy the predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. @inlinable public func countThrowingSubscription( predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, entityDesc: NSEntityDescription, as _: Value.Type ) -> AsyncThrowingStream { @@ -170,6 +287,7 @@ extension CoreDataRepository { let subscription = CountThrowingSubscription( context: context.childContext(), predicate: predicate, + changeTrackingRequest: changeTrackingRequest, entityDesc: entityDesc, continuation: continuation ) @@ -230,9 +348,74 @@ extension CoreDataRepository { /// Subscribe to the max or maximum of a managed object's numeric property for all instances that satisfy the /// predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func maxSubscription( + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncStream> { + AsyncStream { continuation in + let subscription = AggregateSubscription( + function: .max, + context: context.childContext(), + predicate: predicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the max or maximum of a managed object's numeric property for all instances that satisfy the + /// predicate. + @inlinable + public func maxThrowingSubscription( + predicate: NSPredicate, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + let subscription = AggregateThrowingSubscription( + function: .max, + context: context.childContext(), + predicate: predicate, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the max or maximum of a managed object's numeric property for all instances that satisfy the + /// predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. @inlinable public func maxThrowingSubscription( predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, entityDesc: NSEntityDescription, attributeDesc: NSAttributeDescription, groupBy: NSAttributeDescription? = nil, @@ -243,6 +426,7 @@ extension CoreDataRepository { function: .max, context: context.childContext(), predicate: predicate, + changeTrackingRequest: changeTrackingRequest, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy, @@ -303,6 +487,39 @@ extension CoreDataRepository { } } + /// Subscribe to the min or minimum of a managed object's numeric property for all instances that satisfy the + /// predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func minSubscription( + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncStream> { + AsyncStream { continuation in + let subscription = AggregateSubscription( + function: .min, + context: context.childContext(), + predicate: predicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + /// Subscribe to the min or minimum of a managed object's numeric property for all instances that satisfy the /// predicate. @inlinable @@ -330,6 +547,39 @@ extension CoreDataRepository { } } + /// Subscribe to the min or minimum of a managed object's numeric property for all instances that satisfy the + /// predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func minThrowingSubscription( + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + let subscription = AggregateThrowingSubscription( + function: .min, + context: context.childContext(), + predicate: predicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + // MARK: Sum /// Get the sum of a managed object's numeric property for all instances that satisfy the predicate. @@ -378,9 +628,72 @@ extension CoreDataRepository { } /// Subscribe to the sum of a managed object's numeric property for all instances that satisfy the predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func sumSubscription( + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncStream> { + AsyncStream { continuation in + let subscription = AggregateSubscription( + function: .sum, + context: context.childContext(), + predicate: predicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the sum of a managed object's numeric property for all instances that satisfy the predicate. + @inlinable + public func sumThrowingSubscription( + predicate: NSPredicate, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + as _: Value.Type + ) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + let subscription = AggregateThrowingSubscription( + function: .sum, + context: context.childContext(), + predicate: predicate, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy, + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + + /// Subscribe to the sum of a managed object's numeric property for all instances that satisfy the predicate. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. @inlinable public func sumThrowingSubscription( predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, entityDesc: NSEntityDescription, attributeDesc: NSAttributeDescription, groupBy: NSAttributeDescription? = nil, @@ -391,6 +704,7 @@ extension CoreDataRepository { function: .sum, context: context.childContext(), predicate: predicate, + changeTrackingRequest: changeTrackingRequest, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy, diff --git a/Sources/CoreDataRepository/CoreDataRepository+Fetch.swift b/Sources/CoreDataRepository/CoreDataRepository+Fetch.swift index 433c8a6..501b6e0 100644 --- a/Sources/CoreDataRepository/CoreDataRepository+Fetch.swift +++ b/Sources/CoreDataRepository/CoreDataRepository+Fetch.swift @@ -39,6 +39,31 @@ extension CoreDataRepository { } } + /// Fetch items from the store with a ``NSFetchRequest`` and receive updates as the store changes. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func fetchSubscription( + request: NSFetchRequest, + changeTrackingRequest: NSFetchRequest, + of _: Model.Type + ) -> AsyncStream> { + AsyncStream { continuation in + let subscription = FetchSubscription( + fetchRequest: request, + fetchResultControllerRequest: changeTrackingRequest, + context: context.childContext(), + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + /// Fetch items from the store with a ``NSFetchRequest`` and receive updates as the store changes. @inlinable public func fetchThrowingSubscription( @@ -58,6 +83,31 @@ extension CoreDataRepository { } } + /// Fetch items from the store with a ``NSFetchRequest`` and receive updates as the store changes. + /// + /// This endpoint allows separate fetch requests for fetching and change tracking. There are times where CoreData + /// will not recognize changes with a specific predicate. The fix, is to use a simplified predicate for change + /// tracking and the full predicate for fetching. + @inlinable + public func fetchThrowingSubscription( + request: NSFetchRequest, + changeTrackingRequest: NSFetchRequest, + of _: Model.Type + ) -> AsyncThrowingStream<[Model], Error> { + AsyncThrowingStream { continuation in + let subscription = FetchThrowingSubscription( + fetchRequest: request, + fetchResultControllerRequest: changeTrackingRequest, + context: context.childContext(), + continuation: continuation + ) + continuation.onTermination = { _ in + subscription.cancel() + } + subscription.manualFetch() + } + } + /// Fetch items from the store with a ``NSFetchRequest`` and transform the results. @inlinable public func fetch( diff --git a/Sources/CoreDataRepository/Internal/AggregateSubscription.swift b/Sources/CoreDataRepository/Internal/AggregateSubscription.swift index e395ee9..60ae717 100644 --- a/Sources/CoreDataRepository/Internal/AggregateSubscription.swift +++ b/Sources/CoreDataRepository/Internal/AggregateSubscription.swift @@ -40,7 +40,6 @@ final class AggregateSubscription: Subscription: Subscription do { - request = try NSFetchRequest.request( + request = try NSFetchRequest.request( function: function, predicate: predicate, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy ) - } catch let error as CoreDataError { + } catch { self.init( fetchRequest: NSFetchRequest(), fetchResultControllerRequest: NSFetchRequest(), context: context, continuation: continuation ) - self.fail(error) + fail(error) return - } catch let error as CocoaError { + } + guard entityDesc == attributeDesc.entity else { self.init( fetchRequest: NSFetchRequest(), fetchResultControllerRequest: NSFetchRequest(), context: context, continuation: continuation ) - self.fail(.cocoa(error)) + guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else { + fail(.propertyDoesNotMatchEntity(description: nil)) + return + } + guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName + else { + fail(.propertyDoesNotMatchEntity(description: entityName)) + return + } + fail( + .propertyDoesNotMatchEntity( + description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)" + ) + ) return + } + self.init(request: request, context: context, continuation: continuation) + } + + @usableFromInline + convenience init( + function: CoreDataRepository.AggregateFunction, + context: NSManagedObjectContext, + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + continuation: AsyncStream>.Continuation + ) { + let request: NSFetchRequest + do { + request = try NSFetchRequest.request( + function: function, + predicate: predicate, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy + ) } catch { self.init( fetchRequest: NSFetchRequest(), @@ -84,7 +121,7 @@ final class AggregateSubscription: Subscription: Subscription: ThrowingSu } @usableFromInline - // swiftlint:disable:next function_body_length convenience init( function: CoreDataRepository.AggregateFunction, context: NSManagedObjectContext, @@ -52,35 +51,73 @@ final class AggregateThrowingSubscription: ThrowingSu entityDesc: NSEntityDescription, attributeDesc: NSAttributeDescription, groupBy: NSAttributeDescription? = nil, - continuation: AsyncThrowingStream.Continuation + continuation: AsyncThrowingStream.Continuation ) { let request: NSFetchRequest do { - request = try NSFetchRequest.request( + request = try NSFetchRequest.request( function: function, predicate: predicate, entityDesc: entityDesc, attributeDesc: attributeDesc, groupBy: groupBy ) - } catch let error as CoreDataError { + } catch { self.init( fetchRequest: NSFetchRequest(), fetchResultControllerRequest: NSFetchRequest(), context: context, continuation: continuation ) - self.fail(error) + fail(error) return - } catch let error as CocoaError { + } + guard entityDesc == attributeDesc.entity else { self.init( fetchRequest: NSFetchRequest(), fetchResultControllerRequest: NSFetchRequest(), context: context, continuation: continuation ) - self.fail(.cocoa(error)) + guard let entityName = entityDesc.name ?? entityDesc.managedObjectClassName else { + fail(.propertyDoesNotMatchEntity(description: nil)) + return + } + guard let attributeEntityName = attributeDesc.entity.name ?? attributeDesc.entity.managedObjectClassName + else { + fail(.propertyDoesNotMatchEntity(description: entityName)) + return + } + fail( + .propertyDoesNotMatchEntity( + description: "\(entityName) != \(attributeDesc.name).\(attributeEntityName)" + ) + ) return + } + self.init(request: request, context: context, continuation: continuation) + } + + @usableFromInline + convenience init( + function: CoreDataRepository.AggregateFunction, + context: NSManagedObjectContext, + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + attributeDesc: NSAttributeDescription, + groupBy: NSAttributeDescription? = nil, + continuation: AsyncThrowingStream.Continuation + ) { + let request: NSFetchRequest + do { + request = try NSFetchRequest.request( + function: function, + predicate: predicate, + entityDesc: entityDesc, + attributeDesc: attributeDesc, + groupBy: groupBy + ) } catch { self.init( fetchRequest: NSFetchRequest(), @@ -88,7 +125,7 @@ final class AggregateThrowingSubscription: ThrowingSu context: context, continuation: continuation ) - fail(.unknown(error as NSError)) + fail(error) return } guard entityDesc == attributeDesc.entity else { @@ -114,6 +151,11 @@ final class AggregateThrowingSubscription: ThrowingSu ) return } - self.init(request: request, context: context, continuation: continuation) + self.init( + fetchRequest: request, + fetchResultControllerRequest: changeTrackingRequest, + context: context, + continuation: continuation + ) } } diff --git a/Sources/CoreDataRepository/Internal/CountSubscription.swift b/Sources/CoreDataRepository/Internal/CountSubscription.swift index 085a865..29e6ad3 100644 --- a/Sources/CoreDataRepository/Internal/CountSubscription.swift +++ b/Sources/CoreDataRepository/Internal/CountSubscription.swift @@ -14,12 +14,12 @@ final class CountSubscription: Subscription: Subscription do { - request = try NSFetchRequest.countRequest( + request = try NSFetchRequest.countRequest( predicate: predicate, entityDesc: entityDesc ) - } catch let error as CoreDataError { - self.init( - fetchRequest: NSFetchRequest(), - fetchResultControllerRequest: NSFetchRequest(), - context: context, - continuation: continuation - ) - self.fail(error) - return - } catch let error as CocoaError { + } catch { self.init( fetchRequest: NSFetchRequest(), fetchResultControllerRequest: NSFetchRequest(), context: context, continuation: continuation ) - self.fail(.cocoa(error)) + fail(error) return + } + self.init(request: request, context: context, continuation: continuation) + } + + @usableFromInline + convenience init( + context: NSManagedObjectContext, + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + continuation: AsyncStream>.Continuation + ) { + let request: NSFetchRequest + do { + request = try NSFetchRequest.countRequest(predicate: predicate, entityDesc: entityDesc) } catch { self.init( fetchRequest: NSFetchRequest(), @@ -67,9 +73,14 @@ final class CountSubscription: Subscription: ThrowingSubscr { @usableFromInline override func fetch() { - frc.managedObjectContext.perform { [weak self, frc] in + frc.managedObjectContext.perform { [weak self, frc, request] in if (frc.fetchedObjects ?? []).isEmpty { self?.start() } do { - let count = try frc.managedObjectContext.count(for: frc.fetchRequest) + let count = try frc.managedObjectContext.count(for: request) self?.send(Value(exactly: count) ?? Value.zero) } catch let error as CocoaError { self?.fail(.cocoa(error)) @@ -38,32 +38,41 @@ final class CountThrowingSubscription: ThrowingSubscr context: NSManagedObjectContext, predicate: NSPredicate, entityDesc: NSEntityDescription, - continuation: AsyncThrowingStream.Continuation + continuation: AsyncThrowingStream.Continuation ) { let request: NSFetchRequest do { - request = try NSFetchRequest.countRequest( + request = try NSFetchRequest.countRequest( predicate: predicate, entityDesc: entityDesc ) - } catch let error as CoreDataError { + } catch { self.init( fetchRequest: NSFetchRequest(), fetchResultControllerRequest: NSFetchRequest(), context: context, continuation: continuation ) - self.fail(error) + fail(error) return - } catch let error as CocoaError { - self.init( - fetchRequest: NSFetchRequest(), - fetchResultControllerRequest: NSFetchRequest(), - context: context, - continuation: continuation + } + self.init(request: request, context: context, continuation: continuation) + } + + @usableFromInline + convenience init( + context: NSManagedObjectContext, + predicate: NSPredicate, + changeTrackingRequest: NSFetchRequest, + entityDesc: NSEntityDescription, + continuation: AsyncThrowingStream.Continuation + ) { + let request: NSFetchRequest + do { + request = try NSFetchRequest.countRequest( + predicate: predicate, + entityDesc: entityDesc ) - self.fail(.cocoa(error)) - return } catch { self.init( fetchRequest: NSFetchRequest(), @@ -71,9 +80,14 @@ final class CountThrowingSubscription: ThrowingSubscr context: context, continuation: continuation ) - fail(.unknown(error as NSError)) + fail(error) return } - self.init(request: request, context: context, continuation: continuation) + self.init( + fetchRequest: request, + fetchResultControllerRequest: changeTrackingRequest, + context: context, + continuation: continuation + ) } } diff --git a/Sources/CoreDataRepository/Internal/NSFetchRequest+AggregateHelpers.swift b/Sources/CoreDataRepository/Internal/NSFetchRequest+AggregateHelpers.swift index da60166..82b812d 100644 --- a/Sources/CoreDataRepository/Internal/NSFetchRequest+AggregateHelpers.swift +++ b/Sources/CoreDataRepository/Internal/NSFetchRequest+AggregateHelpers.swift @@ -16,7 +16,7 @@ extension NSFetchRequest { entityDesc: NSEntityDescription, attributeDesc: NSAttributeDescription, groupBy: NSAttributeDescription? = nil - ) throws -> NSFetchRequest { + ) throws(CoreDataError) -> NSFetchRequest { guard let entityName = entityDesc.name else { throw CoreDataError.noEntityNameFound } @@ -42,7 +42,7 @@ extension NSFetchRequest { static func countRequest( predicate: NSPredicate, entityDesc: NSEntityDescription - ) throws -> NSFetchRequest { + ) throws(CoreDataError) -> NSFetchRequest { guard let attributeDesc = entityDesc.attributesByName.values.first else { throw CoreDataError.atLeastOneAttributeDescRequired } diff --git a/Tests/CoreDataRepositoryTests/AggregateTests.swift b/Tests/CoreDataRepositoryTests/AggregateTests.swift index 5532f24..89d0371 100644 --- a/Tests/CoreDataRepositoryTests/AggregateTests.swift +++ b/Tests/CoreDataRepositoryTests/AggregateTests.swift @@ -159,6 +159,63 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func countSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let fetchPredicate = NSPredicate(value: true) + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + repository + .countSubscription( + predicate: fetchPredicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + } + } else { + repository + .countSubscription( + predicate: fetchPredicate, + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + } + for await _count in stream { + let count = try _count.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(count, 5, "Result value (count) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(count, 4, "Count should match expected value after deleting one value.") + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func countThrowingSubscription(inTransaction: Bool) async throws { let task = Task { @@ -201,6 +258,61 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func countThrowingSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + repository + .countThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + } + } else { + repository + .countThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + } + for try await count in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(count, 5, "Result value (count) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(count, 4, "Count should match expected value after deleting one value.") + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func sumSuccess(inTransaction: Bool) async throws { let result = if inTransaction { @@ -322,6 +434,72 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func sumSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.sumSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.sumSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for await _sum in stream { + let sum = try _sum.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(sum, 150, "Result value (sum) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + sum, + 100, + "Result value (sum) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func sumThrowingSubscription(inTransaction: Bool) async throws { let task = Task { @@ -374,6 +552,71 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func sumThrowingSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.sumThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.sumThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for try await sum in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(sum, 150, "Result value (sum) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + sum, + 100, + "Result value (sum) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func averageSuccess(inTransaction: Bool) async throws { let result = if inTransaction { @@ -503,6 +746,72 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func averageSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.averageSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.averageSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for await _average in stream { + let average = try _average.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(average, 30, "Result value (average) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + average, + 25, + "Result value (average) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func averageThrowingSubscription(inTransaction: Bool) async throws { let task = Task { @@ -555,6 +864,71 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func averageThrowingSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.averageThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.averageThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for try await average in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(average, 30, "Result value (average) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + average, + 25, + "Result value (average) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func minSuccess(inTransaction: Bool) async throws { let result = if inTransaction { @@ -684,6 +1058,72 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func minSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.minSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.minSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for await _min in stream { + let min = try _min.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(min, 10, "Result value (min) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + min, + 10, + "Result value (min) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func minThrowingSubscription(inTransaction: Bool) async throws { let task = Task { @@ -736,6 +1176,71 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func minThrowingSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.minThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.minThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for try await min in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(min, 10, "Result value (min) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + min, + 10, + "Result value (min) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func maxSuccess(inTransaction: Bool) async throws { let result = if inTransaction { @@ -865,6 +1370,72 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func maxSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.maxSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.maxSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for await _max in stream { + let max = try _max.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(max, 50, "Result value (max) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + max, + 40, + "Result value (max) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func maxThrowingSubscription(inTransaction: Bool) async throws { let task = Task { @@ -917,6 +1488,71 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func maxThrowingSubscriptionWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = try #require(UnmanagedModel_UuidId + .managedFetchRequest() as? NSFetchRequest) + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 30), + modifier: .direct, + type: .notEqualTo + ) + changeTrackingRequest.sortDescriptors = [ + NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true), + ] + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + try repository.maxThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + } else { + try repository.maxThrowingSubscription( + predicate: NSPredicate(value: true), + changeTrackingRequest: changeTrackingRequest, + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + } + for try await max in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(max, 50, "Result value (max) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + max, + 40, + "Result value (max) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func countWithPredicate(inTransaction: Bool) async throws { let result = if inTransaction { diff --git a/Tests/CoreDataRepositoryTests/FetchTests.swift b/Tests/CoreDataRepositoryTests/FetchTests.swift index fa3c083..ebd3279 100644 --- a/Tests/CoreDataRepositoryTests/FetchTests.swift +++ b/Tests/CoreDataRepositoryTests/FetchTests.swift @@ -110,6 +110,62 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func fetchSubscriptionSuccessWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = Self.fetchRequest() + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 3), + modifier: .direct, + type: .notEqualTo + ) + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + repository + .fetchSubscription( + request: Self.fetchRequest(), + changeTrackingRequest: changeTrackingRequest, + of: FetchableModel_UuidId.self + ) + } + } else { + repository + .fetchSubscription( + request: Self.fetchRequest(), + changeTrackingRequest: changeTrackingRequest, + of: FetchableModel_UuidId.self + ) + } + for await _items in stream { + let items = try _items.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(items.count, 5, "Result items count should match expectation") + expectNoDifference(items, expectedValues, "Result items should match expectations") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(items.count, 4, "Result items count should match expectation") + expectNoDifference( + items, + Array(expectedValues[0 ... 3]), + "Result items should match expectations" + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + @Test(arguments: [false, true]) func fetchThrowingSubscriptionSuccess(inTransaction: Bool) async throws { let task = Task { @@ -154,6 +210,59 @@ extension CoreDataRepositoryTests { expectNoDifference(finalCount, 2) } + @Test(arguments: [false, true]) + func fetchThrowingSubscriptionSuccessWithSplitFetchRequests(inTransaction: Bool) async throws { + let changeTrackingRequest = Self.fetchRequest() + changeTrackingRequest.predicate = NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.int), + rightExpression: NSExpression(forConstantValue: 3), + modifier: .direct, + type: .notEqualTo + ) + let task = Task { + var resultCount = 0 + let stream = if inTransaction { + try await repository.withTransaction { _ in + repository.fetchThrowingSubscription( + request: Self.fetchRequest(), + changeTrackingRequest: changeTrackingRequest, + of: FetchableModel_UuidId.self + ) + } + } else { + repository.fetchThrowingSubscription( + request: Self.fetchRequest(), + changeTrackingRequest: changeTrackingRequest, + of: FetchableModel_UuidId.self + ) + } + for try await items in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(items.count, 5, "Result items count should match expectation") + expectNoDifference(items, expectedValues, "Result items should match expectations") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(items.count, 4, "Result items count should match expectation") + expectNoDifference( + items, + Array(expectedValues[0 ... 3]), + "Result items should match expectations" + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + init( container: NSPersistentContainer, repositoryContext: NSManagedObjectContext, diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..7f0c035 --- /dev/null +++ b/mise.toml @@ -0,0 +1,7 @@ +[tools] +swiftformat = "latest" +swiftlint = "latest" + +[tool_alias] +swiftformat = 'asdf:https://github.com/MFB-Technologies-Inc/asdf-swiftformat' +swiftlint = 'asdf:https://github.com/MFB-Technologies-Inc/asdf-swiftlint'