Skip to content

Commit

Permalink
Revert
Browse files Browse the repository at this point in the history
  • Loading branch information
cuongnv219 committed Dec 22, 2024
1 parent f1fd176 commit a19df71
Show file tree
Hide file tree
Showing 30 changed files with 1,484 additions and 11 deletions.
16 changes: 16 additions & 0 deletions CoreDataRepository/Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "CombineExt",
"repositoryURL": "https://github.com/CombineCommunity/CombineExt.git",
"state": {
"branch": null,
"revision": "d7b896fa9ca8b47fa7bcde6b43ef9b70bf8c1f56",
"version": "1.8.1"
}
}
]
},
"version": 1
}
33 changes: 33 additions & 0 deletions CoreDataRepository/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "CoreDataRepository",
defaultLocalization: "en",
platforms: [
.iOS(.v15),
.macOS(.v12),
.tvOS(.v15),
.watchOS(.v8),
],
products: [
.library(
name: "CoreDataRepository",
targets: ["CoreDataRepository"]
),
],
dependencies: [
.package(url: "https://github.com/CombineCommunity/CombineExt.git", .upToNextMajor(from: "1.5.1"))
],
targets: [
.target(
name: "CoreDataRepository",
dependencies: [
.product(name: "CombineExt", package: "CombineExt"),
],
path: "./Sources"
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// CoreDataRepository+Aggregate.swift
// CoreDataRepository
//
//
// MIT License
//
// Copyright © 2023 Andrew Roan

import Combine
import CoreData

extension CoreDataRepository {
// MARK: Types

/// The aggregate function to be calculated
public enum AggregateFunction: String {
case count
case sum
case average
case min
case max
}

// MARK: Private Functions

private func request(
function: AggregateFunction,
predicate: NSPredicate,
entityDesc: NSEntityDescription,
attributeDesc: NSAttributeDescription,
groupBy: NSAttributeDescription? = nil
) -> NSFetchRequest<NSDictionary> {
let expDesc = NSExpressionDescription.aggregate(function: function, attributeDesc: attributeDesc)
let request = NSFetchRequest<NSDictionary>(entityName: entityDesc.managedObjectClassName)
request.predicate = predicate
request.entity = entityDesc
request.returnsObjectsAsFaults = false
request.resultType = .dictionaryResultType
if function == .count {
request.propertiesToFetch = [attributeDesc.name, expDesc]
} else {
request.propertiesToFetch = [expDesc]
}

if let groupBy = groupBy {
request.propertiesToGroupBy = [groupBy.name]
}
request.sortDescriptors = [NSSortDescriptor(key: attributeDesc.name, ascending: false)]
return request
}

/// Calculates aggregate values
/// - Parameters
/// - function: Function
/// - predicate: NSPredicate
/// - entityDesc: NSEntityDescription
/// - attributeDesc: NSAttributeDescription
/// - groupBy: NSAttributeDescription? = nil
/// - Returns
/// - `[[String: Value]]`
///
private static func aggregate<Value: Numeric>(
context: NSManagedObjectContext,
request: NSFetchRequest<NSDictionary>
) throws -> [[String: Value]] {
let result = try context.fetch(request)
return result as? [[String: Value]] ?? []
}

private static func send<Value>(
context: NSManagedObjectContext,
request: NSFetchRequest<NSDictionary>
) async -> Result<[[String: Value]], CoreDataRepositoryError> where Value: Numeric {
await context.performInScratchPad { scratchPad in
do {
let result: [[String: Value]] = try Self.aggregate(context: scratchPad, request: request)
return result
} catch {
throw CoreDataRepositoryError.coreData(error as NSError)
}
}
}

// MARK: Public Functions

/// Calculate the count for a fetchRequest
/// - Parameters:
/// - predicate: NSPredicate
/// - entityDesc: NSEntityDescription
/// - Returns
/// - Result<[[String: Value]], CoreDataRepositoryError>
///
public func count<Value: Numeric>(
predicate: NSPredicate,
entityDesc: NSEntityDescription
) async -> Result<[[String: Value]], CoreDataRepositoryError> {
let _request = NSFetchRequest<NSDictionary>(entityName: entityDesc.name ?? "")
_request.predicate = predicate
_request
.sortDescriptors =
[NSSortDescriptor(key: entityDesc.attributesByName.values.first!.name, ascending: true)]
return await context.performInScratchPad { scratchPad in
do {
let count = try scratchPad.count(for: _request)
return [["countOf\(entityDesc.name ?? "")": Value(exactly: count) ?? Value.zero]]
} catch {
throw CoreDataRepositoryError.coreData(error as NSError)
}
}
}

/// Calculate the sum for a fetchRequest
/// - Parameters:
/// - predicate: NSPredicate
/// - entityDesc: NSEntityDescription
/// - attributeDesc: NSAttributeDescription
/// - groupBy: NSAttributeDescription? = nil
/// - Returns
/// - Result<[[String: Value]], CoreDataRepositoryError>
///
public func sum<Value: Numeric>(
predicate: NSPredicate,
entityDesc: NSEntityDescription,
attributeDesc: NSAttributeDescription,
groupBy: NSAttributeDescription? = nil
) async -> Result<[[String: Value]], CoreDataRepositoryError> {
let _request = request(
function: .sum,
predicate: predicate,
entityDesc: entityDesc,
attributeDesc: attributeDesc,
groupBy: groupBy
)
guard entityDesc == attributeDesc.entity else {
return .failure(.propertyDoesNotMatchEntity)
}
return await Self.send(context: context, request: _request)
}

/// Calculate the average for a fetchRequest
/// - Parameters:
/// - predicate: NSPredicate
/// - entityDesc: NSEntityDescription
/// - attributeDesc: NSAttributeDescription
/// - groupBy: NSAttributeDescription? = nil
/// - Returns
/// - Result<[[String: Value]], CoreDataRepositoryError>
///
public func average<Value: Numeric>(
predicate: NSPredicate,
entityDesc: NSEntityDescription,
attributeDesc: NSAttributeDescription,
groupBy: NSAttributeDescription? = nil
) async -> Result<[[String: Value]], CoreDataRepositoryError> {
let _request = request(
function: .average,
predicate: predicate,
entityDesc: entityDesc,
attributeDesc: attributeDesc,
groupBy: groupBy
)
guard entityDesc == attributeDesc.entity else {
return .failure(.propertyDoesNotMatchEntity)
}
return await Self.send(context: context, request: _request)
}

/// Calculate the min for a fetchRequest
/// - Parameters:
/// - predicate: NSPredicate
/// - entityDesc: NSEntityDescription
/// - attributeDesc: NSAttributeDescription
/// - groupBy: NSAttributeDescription? = nil
/// - Returns
/// - Result<[[String: Value]], CoreDataRepositoryError>
///
public func min<Value: Numeric>(
predicate: NSPredicate,
entityDesc: NSEntityDescription,
attributeDesc: NSAttributeDescription,
groupBy: NSAttributeDescription? = nil
) async -> Result<[[String: Value]], CoreDataRepositoryError> {
let _request = request(
function: .min,
predicate: predicate,
entityDesc: entityDesc,
attributeDesc: attributeDesc,
groupBy: groupBy
)
guard entityDesc == attributeDesc.entity else {
return .failure(.propertyDoesNotMatchEntity)
}
return await Self.send(context: context, request: _request)
}

/// Calculate the max for a fetchRequest
/// - Parameters:
/// - predicate: NSPredicate
/// - entityDesc: NSEntityDescription
/// - attributeDesc: NSAttributeDescription
/// - groupBy: NSAttributeDescription? = nil
/// - Returns
/// - Result<[[String: Value]], CoreDataRepositoryError>
///
public func max<Value: Numeric>(
predicate: NSPredicate,
entityDesc: NSEntityDescription,
attributeDesc: NSAttributeDescription,
groupBy: NSAttributeDescription? = nil
) async -> Result<[[String: Value]], CoreDataRepositoryError> {
let _request = request(
function: .max,
predicate: predicate,
entityDesc: entityDesc,
attributeDesc: attributeDesc,
groupBy: groupBy
)
guard entityDesc == attributeDesc.entity else {
return .failure(.propertyDoesNotMatchEntity)
}
return await Self.send(context: context, request: _request)
}
}

// MARK: Extensions

extension NSExpression {
/// Convenience initializer for NSExpression that represent an aggregate function on a keypath
fileprivate convenience init(
function: CoreDataRepository.AggregateFunction,
attributeDesc: NSAttributeDescription
) {
let keyPathExp = NSExpression(forKeyPath: attributeDesc.name)
self.init(forFunction: "\(function.rawValue):", arguments: [keyPathExp])
}
}

extension NSExpressionDescription {
/// Convenience initializer for NSExpressionDescription that represent the properties to fetch in NSFetchRequest
fileprivate static func aggregate(
function: CoreDataRepository.AggregateFunction,
attributeDesc: NSAttributeDescription
) -> NSExpressionDescription {
let expression = NSExpression(function: function, attributeDesc: attributeDesc)
let expDesc = NSExpressionDescription()
expDesc.expression = expression
expDesc.name = "\(function.rawValue)Of\(attributeDesc.name.capitalized)"
expDesc.expressionResultType = attributeDesc.attributeType
return expDesc
}
}
Loading

0 comments on commit a19df71

Please sign in to comment.