// // Where.Expression.swift // CoreStore // // Copyright © 2018 John Rommel Estropia // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // import Foundation import CoreData // MARK: - WhereExpressionTrait /** Used only for `Where.Expression` type constraints. Currently supports `SingleTarget` and `CollectionTarget`. */ public protocol WhereExpressionTrait {} // MARK: - Where extension Where { // MARK: - Expression /** Type-safe keyPath chain usable in query/fetch expressions. ``` let expression: Where.Expression = (\.master ~ \.name) let owner = dataStack.fetchOne( From().where(expression == "John") ) ``` */ public struct Expression: CustomStringConvertible, KeyPathStringConvertible { /** Currently supports `SingleTarget` and `CollectionTarget`. */ public typealias Trait = T // MARK: AnyKeyPathStringConvertible public let cs_keyPathString: String // MARK: KeyPathStringConvertible public typealias ObjectType = O public typealias DestinationValueType = V // MARK: CustomStringConvertible public var description: String { return self.cs_keyPathString } // MARK: Internal internal init(_ component: String) { self.cs_keyPathString = component } internal init(_ component1: String, _ component2: String) { self.cs_keyPathString = component1 + "." + component2 } } // MARK: - SingleTarget /** Used only for `Where.Expression` type constraints. Specifies that this `Where.Expression` type pertains to an attribute property expression. */ public enum SingleTarget: WhereExpressionTrait {} // MARK: - CollectionTarget /** Used only for `Where.Expression` type constraints. Specifies that this `Where.Expression` type pertains to a to-many relationship expression. */ public enum CollectionTarget: WhereExpressionTrait {} } // MARK: - ~ (Where.Expression Creation Operators) // MARK: ~ where D: NSManagedObject /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let owner = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func ~( _ lhs: KeyPath, _ rhs: KeyPath ) -> Where.Expression.SingleTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let owner = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func ~ ( _ lhs: KeyPath, _ rhs: KeyPath ) -> Where.Expression.SingleTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let happyPets = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func ~ ( _ lhs: KeyPath, _ rhs: KeyPath ) -> Where.Expression.CollectionTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let happyPets = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func ~ ( _ lhs: KeyPath, _ rhs: KeyPath ) -> Where.Expression.CollectionTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let johnsSonInLaw = dataStack.fetchOne(From().where((\.spouse ~ \.father ~ \.name) == "John")) ``` */ public func ~ ( _ lhs: Where.Expression, _ rhs: KeyPath ) -> Where.Expression { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let johnsSonInLaw = dataStack.fetchOne(From().where((\.spouse ~ \.father ~ \.name) == "John")) ``` */ public func ~ ( _ lhs: Where.Expression, _ rhs: KeyPath ) -> Where.Expression { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let spouseHasSiblings = dataStack.fetchOne(From().where((\.spouse ~ \.father ~ \.children).count() > 0)) ``` */ public func ~ ( _ lhs: Where.Expression, _ rhs: KeyPath ) -> Where.Expression.CollectionTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let spouseHasSiblings = dataStack.fetchOne(From().where((\.spouse ~ \.father ~ \.children).count() > 0)) ``` */ public func ~ ( _ lhs: Where.Expression, _ rhs: KeyPath ) -> Where.Expression.CollectionTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let spousesWithBadNamingSense = dataStack.fetchAll(From().where((\.spouse ~ \.pets ~ \.name).any() == "Spot")) ``` */ public func ~ ( _ lhs: Where.Expression, _ rhs: KeyPath ) -> Where.Expression.CollectionTarget, V> { return .init( lhs.cs_keyPathString, rhs.cs_keyPathString ) } // MARK: - ~ where D: CoreStoreObject /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let owner = dataStack.fetchOne(From().where((\.$master ~ \.$name) == "John")) ``` */ public func ~ ( _ lhs: KeyPath.Relationship>, _ rhs: KeyPath ) -> Where.Expression.SingleTarget, K.DestinationValueType> where K.ObjectType == D.DestinationObjectType { return .init( O.meta[keyPath: lhs].cs_keyPathString, D.DestinationObjectType.meta[keyPath: rhs].cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let owner = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func ~ (_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression where K.ObjectType == D { return .init( lhs.cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let owner = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func ~ (_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression where K.ObjectType == D { return .init( lhs.cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let happyPets = dataStack.fetchAll(From().where((\.$master ~ \.$pets).count() > 1)) ``` */ public func ~ (_ lhs: KeyPath.Relationship>, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.DestinationValueType> where K.ObjectType == D.DestinationObjectType { return .init( O.meta[keyPath: lhs].cs_keyPathString, D.DestinationObjectType.meta[keyPath: rhs].cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let happyPets = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func ~ (_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.DestinationValueType> where K.ObjectType == O { return .init( lhs.cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let happyPets = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func ~ (_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.DestinationValueType> where K.ObjectType == O { return .init( lhs.cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) } /** Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions ``` let spousesWithBadNamingSense = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.name).any() == "Spot")) ``` */ public func ~ (_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, KV.DestinationValueType> where KC.ObjectType == O, KV.ObjectType == D { return .init( lhs.cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) } // MARK: - Where.Expression where V: QueryableAttributeType /** Creates a `Where` clause by comparing if an expression is equal to a value ``` let dog = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func == (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(lhs.cs_keyPathString, isEqualTo: rhs) } /** Creates a `Where` clause by comparing if an expression is not equal to a value ``` let dog = dataStack.fetchOne(From().where((\.master ~ \.name) != "John")) ``` */ public func != (_ lhs: Where.Expression, _ rhs: V) -> Where { return !Where(lhs.cs_keyPathString, isEqualTo: rhs) } /** Creates a `Where` clause by checking if a sequence contains a value ``` let dog = dataStack.fetchOne(From().where(["John", "Joe"] ~= (\.master ~ \.name)) ``` */ public func ~= (_ sequence: S, _ expression: Where.Expression) -> Where where S.Iterator.Element == V { return Where(expression.cs_keyPathString, isMemberOf: sequence) } // MARK: - Where.Expression where V: QueryableAttributeType & Comparable /** Creates a `Where` clause by comparing if an expression is less than a value ``` let lonelyDog = dataStack.fetchOne(From().where((\.master ~ \.pets).count() < 2)) ``` */ public func < (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(expression: lhs, function: "<", operand: rhs) } /** Creates a `Where` clause by comparing if an expression is less than or equal to a value ``` let lonelyDog = dataStack.fetchOne(From().where((\.master ~ \.pets).count() <= 1) ``` */ public func <= (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(expression: lhs, function: "<=", operand: rhs) } /** Creates a `Where` clause by comparing if an expression is greater than a value ``` let happyDog = dataStack.fetchOne(From().where((\.master ~ \.pets).count() > 1) ``` */ public func > (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(expression: lhs, function: ">", operand: rhs) } /** Creates a `Where` clause by comparing if an expression is greater than or equal to a value ``` let happyDog = dataStack.fetchOne(From().where((\.master ~ \.pets).count() >= 2) ``` */ public func >= (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(expression: lhs, function: ">=", operand: rhs) } // MARK: - Where.Expression where V: Optional /** Creates a `Where` clause by comparing if an expression is equal to a value ``` let dog = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func == (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(lhs.cs_keyPathString, isEqualTo: rhs) } /** Creates a `Where` clause by comparing if an expression is equal to a value ``` let dog = dataStack.fetchOne(From().where((\.master ~ \.name) == "John")) ``` */ public func == (_ lhs: Where.Expression, _ rhs: V?) -> Where { return Where(lhs.cs_keyPathString, isEqualTo: rhs) } /** Creates a `Where` clause by comparing if an expression is equal to a value ``` let dog = dataStack.fetchOne(From().where((\.master ~ \.name) != "John")) ``` */ public func != (_ lhs: Where.Expression, _ rhs: V) -> Where { return !Where(lhs.cs_keyPathString, isEqualTo: rhs) } /** Creates a `Where` clause by comparing if an expression is equal to a value ``` let dog = dataStack.fetchOne(From().where((\.master ~ \.name) != "John")) ``` */ public func != (_ lhs: Where.Expression, _ rhs: V?) -> Where { return !Where(lhs.cs_keyPathString, isEqualTo: rhs) } /** Creates a `Where` clause by checking if a sequence contains a value ``` let dog = dataStack.fetchOne(From().where(["John", "Joe"] ~= (\.master ~ \.name)) ``` */ public func ~= (_ sequence: S, _ expression: Where.Expression) -> Where where S.Iterator.Element == V { return Where(expression.cs_keyPathString, isMemberOf: sequence) } // MARK: - Where.Expression where V: Optional /** Creates a `Where` clause by comparing if an expression is less than a value ``` let childsPet = dataStack.fetchOne(From().where((\.master ~ \.age) < 10)) ``` */ public func < (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(expression: lhs, function: "<", operand: rhs) } /** Creates a `Where` clause by comparing if an expression is less than or equal to a value ``` let childsPet = dataStack.fetchOne(From().where((\.master ~ \.age) <= 10)) ``` */ public func <= (_ lhs: Where.Expression, _ rhs: V?) -> Where { return Where(expression: lhs, function: "<=", operand: rhs) } /** Creates a `Where` clause by comparing if an expression is greater than a value ``` let teensPet = dataStack.fetchOne(From().where((\.master ~ \.age) > 10)) ``` */ public func > (_ lhs: Where.Expression, _ rhs: V) -> Where { return Where(expression: lhs, function: ">", operand: rhs) } /** Creates a `Where` clause by comparing if an expression is greater than or equal to a value ``` let teensPet = dataStack.fetchOne(From().where((\.master ~ \.age) >= 10)) ``` */ public func >= (_ lhs: Where.Expression, _ rhs: V?) -> Where { return Where(expression: lhs, function: ">=", operand: rhs) } // MARK: - KeyPath where Root: NSManagedObject, Value: AllowedObjectiveCToManyRelationshipKeyPathValue extension KeyPath where Root: NSManagedObject, Value: AllowedObjectiveCToManyRelationshipKeyPathValue { /** Creates a `Where.Expression` clause for COUNT ``` let dogsWithPlaymates = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func count() -> Where.Expression.CollectionTarget, Int> { return .init(self.cs_keyPathString, "@count") } } // MARK: - Where.Expression where O: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCToManyRelationshipKeyPathValue extension Where.Expression where O: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCToManyRelationshipKeyPathValue { /** Creates a `Where.Expression` clause for COUNT ``` let dogsWithPlaymates = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func count() -> Where.Expression { return .init(self.cs_keyPathString, "@count") } } // MARK: - Where.Expression where O: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCKeyPathValue extension Where.Expression where O: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCKeyPathValue { /** Creates a `Where.Expression` clause for ANY ``` let dogsWithBadNamingSense = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.name).any() > "Spot")) ``` */ public func any() -> Where.Expression { return .init("ANY " + self.cs_keyPathString) } /** Creates a `Where.Expression` clause for ALL ``` let allPlaymatePuppies = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.age).all() > 5)) ``` */ public func all() -> Where.Expression { return .init("ALL " + self.cs_keyPathString) } /** Creates a `Where.Expression` clause for NONE ``` let dogs = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.name).any() > "Spot")) ``` */ public func none() -> Where.Expression { return .init("NONE " + self.cs_keyPathString) } } // MARK: - KeyPath where Root: CoreStoreObject, Value: AllowedObjectiveCToManyRelationshipKeyPathValue extension KeyPath where Root: CoreStoreObject, Value: ToManyRelationshipKeyPathStringConvertible { /** Creates a `Where.Expression` clause for COUNT ``` let dogsWithPlaymates = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func count() -> Where.Expression.CollectionTarget, Int> { return .init(Root.meta[keyPath: self].cs_keyPathString, "@count") } } // MARK: - Where.Expression where O: CoreStoreObject, T == Where.CollectionTarget extension Where.Expression where O: CoreStoreObject, T == Where.CollectionTarget { /** Creates a `Where.Expression` clause for COUNT ``` let dogsWithPlaymates = dataStack.fetchAll(From().where((\.master ~ \.pets).count() > 1)) ``` */ public func count() -> Where.Expression { return .init(self.cs_keyPathString, "@count") } /** Creates a `Where.Expression` clause for ANY ``` let dogsWithBadNamingSense = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.name).any() > "Spot")) ``` */ public func any() -> Where.Expression { return .init("ANY " + self.cs_keyPathString) } /** Creates a `Where.Expression` clause for ALL ``` let allPlaymatePuppies = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.age).all() > 5)) ``` */ public func all() -> Where.Expression { return .init("ALL " + self.cs_keyPathString) } /** Creates a `Where.Expression` clause for NONE ``` let dogs = dataStack.fetchAll(From().where((\.master ~ \.pets ~ \.name).any() > "Spot")) ``` */ public func none() -> Where.Expression { return .init("NONE " + self.cs_keyPathString) } } // MARK: - Where extension Where { // MARK: FilePrivate fileprivate init(expression: Where.Expression, function: String, operand: V) { self.init("\(expression.cs_keyPathString) \(function) %@", operand.cs_toQueryableNativeType()) } fileprivate init(expression: Where.Expression, function: String, operand: V) { self.init("\(expression.cs_keyPathString) \(function) %@", operand.cs_toQueryableNativeType()) } fileprivate init(expression: Where.Expression, function: String, operand: V?) { if let operand = operand { self.init("\(expression.cs_keyPathString) \(function) %@", operand.cs_toQueryableNativeType()) } else { self.init("\(expression.cs_keyPathString) \(function) nil") } } fileprivate init(expression: Where.Expression, function: String, operand: V?) { if let operand = operand { self.init("\(expression.cs_keyPathString) \(function) %@", operand.cs_toQueryableNativeType()) } else { self.init("\(expression.cs_keyPathString) \(function) nil") } } } // MARK: - Deprecated @available(*, deprecated, message: """ Legacy `Value.*`, `Transformable.*`, and `Relationship.*` declarations will soon be obsoleted. Please migrate your models and stores to new models that use `@Field.*` property wrappers. See: https://github.com/JohnEstropia/CoreStore?tab=readme-ov-file#new-field-property-wrapper-syntax """) public func ~ (_ lhs: KeyPath.ToOne>, _ rhs: KeyPath) -> Where.Expression.SingleTarget, K.DestinationValueType> where K.ObjectType == D { return .init( O.meta[keyPath: lhs].cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) } @available(*, deprecated, message: """ Legacy `Value.*`, `Transformable.*`, and `Relationship.*` declarations will soon be obsoleted. Please migrate your models and stores to new models that use `@Field.*` property wrappers. See: https://github.com/JohnEstropia/CoreStore?tab=readme-ov-file#new-field-property-wrapper-syntax """) public func ~ (_ lhs: KeyPath.ToOne>, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.DestinationValueType> where K.ObjectType == D { return .init( O.meta[keyPath: lhs].cs_keyPathString, D.meta[keyPath: rhs].cs_keyPathString ) }