diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 735bd39..2489be8 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -578,6 +578,8 @@ B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; }; B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; }; B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; }; + B5DAFB482203D9F8003FCCD0 /* Where.Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAFB472203D9F8003FCCD0 /* Where.Expression.swift */; }; + B5DAFB4A2203E01D003FCCD0 /* KeyPathGenericBindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAFB492203E01D003FCCD0 /* KeyPathGenericBindings.swift */; }; B5DBE2CD1C9914A900B5CEFA /* CSCoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBE2CC1C9914A900B5CEFA /* CSCoreStore.swift */; }; B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBE2CC1C9914A900B5CEFA /* CSCoreStore.swift */; }; B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBE2CC1C9914A900B5CEFA /* CSCoreStore.swift */; }; @@ -902,6 +904,8 @@ B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacySQLiteStore.swift; sourceTree = ""; }; B5D7A5B51CA3BF8F005C752B /* CSInto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSInto.swift; sourceTree = ""; }; B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = CoreStore.podspec; sourceTree = SOURCE_ROOT; }; + B5DAFB472203D9F8003FCCD0 /* Where.Expression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Where.Expression.swift; sourceTree = ""; }; + B5DAFB492203E01D003FCCD0 /* KeyPathGenericBindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathGenericBindings.swift; sourceTree = ""; }; B5DBE2CC1C9914A900B5CEFA /* CSCoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSCoreStore.swift; sourceTree = ""; }; B5DBE2D11C991B3E00B5CEFA /* CSDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSDataStack.swift; sourceTree = ""; }; B5DBE2DA1C9939E100B5CEFA /* CoreStoreTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CoreStoreTests-Bridging-Header.h"; sourceTree = ""; }; @@ -1333,6 +1337,7 @@ B5A1DAC71F111BFA003CF369 /* KeyPath+Querying.swift */, B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */, B5CA2B111F81DBFE004B1936 /* DynamicKeyPath.swift */, + B5DAFB492203E01D003FCCD0 /* KeyPathGenericBindings.swift */, ); name = "KeyPath Utilities"; sourceTree = ""; @@ -1480,6 +1485,7 @@ B5E84F011AFF847B0064E85B /* From.swift */, B5E84F031AFF847B0064E85B /* Select.swift */, B5E84F051AFF847B0064E85B /* Where.swift */, + B5DAFB472203D9F8003FCCD0 /* Where.Expression.swift */, B5E84F041AFF847B0064E85B /* OrderBy.swift */, B5E84F021AFF847B0064E85B /* GroupBy.swift */, B5E84F001AFF847B0064E85B /* Tweak.swift */, @@ -1896,6 +1902,7 @@ B5ECDBFF1CA80CBA00C7F112 /* CSWhere.swift in Sources */, B5ECDC051CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5E1B5981CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, + B5DAFB4A2203E01D003FCCD0 /* KeyPathGenericBindings.swift in Sources */, B5519A5F1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AA1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74411E9B8724005F3DAC /* UnsafeDataModelSchema.swift in Sources */, @@ -1935,6 +1942,7 @@ B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */, B51260931E9B28F100402229 /* EntityIdentifier.swift in Sources */, + B5DAFB482203D9F8003FCCD0 /* Where.Expression.swift in Sources */, B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */, B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D21C991B3E00B5CEFA /* CSDataStack.swift in Sources */, @@ -2709,7 +2717,7 @@ SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 10.0; VERSIONING_SYSTEM = "apple-generic"; @@ -2771,7 +2779,7 @@ SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 10.0; VALIDATE_PRODUCT = YES; diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index cc0ce3a..6ba5a13 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -30,9 +30,10 @@ import CoreStore #if os(macOS) typealias Color = NSColor + #else - typealias Color = UIColor + #endif class Animal: CoreStoreObject { @@ -134,9 +135,11 @@ class DynamicModelTests: BaseTestDataTestCase { let k1 = String(keyPath: \Animal.species) XCTAssertEqual(k1, "species") - + + #if swift(<5.0) let k2 = String(keyPath: \Dog.species) XCTAssertEqual(k2, "species") + #endif let k3 = String(keyPath: \Dog.nickname) XCTAssertEqual(k3, "nickname") @@ -272,11 +275,11 @@ class DynamicModelTests: BaseTestDataTestCase { _ = try transaction.fetchAll( From() - .where(\Animal.species == "Dog" && \.age == 10) + .where(\Animal.species == "Dog" && \Dog.age == 10) ) _ = try transaction.fetchAll( From() - .where(\.age == 10 && \Animal.species == "Dog") + .where(\Dog.age == 10 && \Animal.species == "Dog") .orderBy(.ascending({ $0.species })) ) _ = try transaction.fetchAll( @@ -317,7 +320,9 @@ class DynamicModelTests: BaseTestDataTestCase { dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() { XCTAssertEqual(String(keyPath: \Animal.species), "species") + #if swift(<5.0) XCTAssertEqual(String(keyPath: \Dog.species), "species") + #endif } @nonobjc diff --git a/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents b/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents index 3aae67e..e5461b7 100644 --- a/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents +++ b/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -9,6 +9,8 @@ + + @@ -19,6 +21,8 @@ + + @@ -27,7 +31,7 @@ - - + + \ No newline at end of file diff --git a/CoreStoreTests/SetupTests.swift b/CoreStoreTests/SetupTests.swift index 8750299..b053c00 100644 --- a/CoreStoreTests/SetupTests.swift +++ b/CoreStoreTests/SetupTests.swift @@ -330,7 +330,14 @@ class SetupTests: BaseTestDataTestCase { xcodeModelName: "Model", bundle: Bundle(for: type(of: self)) ) - try! stack.addStorageAndWait(sqliteStore) + try! stack.addStorageAndWait( + SQLiteStore.legacy( + fileName: sqliteStore.fileURL.lastPathComponent, + configuration: sqliteStore.configuration, + migrationMappingProviders: sqliteStore.migrationMappingProviders, + localStorageOptions: .recreateStoreOnModelMismatch + ) + ) self.prepareTestDataForStack(stack) } XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) diff --git a/CoreStoreTests/TestEntities/TestEntity1.swift b/CoreStoreTests/TestEntities/TestEntity1.swift index f7cdd19..8702bf4 100644 --- a/CoreStoreTests/TestEntities/TestEntity1.swift +++ b/CoreStoreTests/TestEntities/TestEntity1.swift @@ -36,4 +36,6 @@ class TestEntity1: NSManagedObject { @NSManaged var testDecimal: NSDecimalNumber? @NSManaged var testData: Data? @NSManaged var testNil: String? + @NSManaged var testToOne: TestEntity1? + @NSManaged var testToManyUnordered: NSSet? } diff --git a/CoreStoreTests/TestEntities/TestEntity2.swift b/CoreStoreTests/TestEntities/TestEntity2.swift index f4484a8..cea3efe 100644 --- a/CoreStoreTests/TestEntities/TestEntity2.swift +++ b/CoreStoreTests/TestEntities/TestEntity2.swift @@ -36,4 +36,6 @@ class TestEntity2: NSManagedObject { @NSManaged var testDecimal: NSDecimalNumber? @NSManaged var testData: Data? @NSManaged var testNil: String? + @NSManaged var testToOne: TestEntity2? + @NSManaged var testToManyOrdered: NSOrderedSet? } diff --git a/CoreStoreTests/WhereTests.swift b/CoreStoreTests/WhereTests.swift index a384147..26e9122 100644 --- a/CoreStoreTests/WhereTests.swift +++ b/CoreStoreTests/WhereTests.swift @@ -56,6 +56,163 @@ final class WhereTests: XCTestCase { dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() { XCTAssertEqual(String(keyPath: \TestEntity1.testEntityID), "testEntityID") + XCTAssertEqual(String(keyPath: \Animal.color), "color") + } + + @objc + dynamic func test_ThatExpressions_HaveCorrectKeyPaths() { + + do { + + XCTAssertEqual( + #keyPath(TestEntity1.testToOne.testEntityID), + String(keyPath: \TestEntity1.testToOne ~ \.testEntityID) + ) + XCTAssertEqual( + #keyPath(TestEntity1.testToOne.testToOne.testToManyUnordered), + String(keyPath: \TestEntity1.testToOne ~ \.testToOne ~ \.testToManyUnordered) + ) + XCTAssertEqual( + #keyPath(TestEntity2.testToOne.testToOne.testToManyOrdered), + String(keyPath: \TestEntity2.testToOne ~ \.testToOne ~ \.testToManyOrdered) + ) + } + do { + + XCTAssertEqual( + "master.pets", + String(keyPath: \Animal.master ~ \.pets) + ) + XCTAssertEqual( + "master.pets.species", + String(keyPath: \Animal.master ~ \.pets ~ \.species) + ) + XCTAssertEqual( + "master.pets.master", + String(keyPath: \Animal.master ~ \.pets ~ \.master) + ) + } + do { + + XCTAssertEqual( + #keyPath(TestEntity1.testToOne.testToManyUnordered) + ".@count", + (\TestEntity1.testToOne ~ \.testToManyUnordered).count().description + ) + XCTAssertEqual( + #keyPath(TestEntity2.testToOne.testToOne.testToManyOrdered) + ".@count", + (\TestEntity2.testToOne ~ \.testToOne ~ \.testToManyOrdered).count().description + ) + } + do { + + XCTAssertEqual( + "master.pets.@count", + (\Animal.master ~ \.pets).count().description + ) + } + do { + + XCTAssertEqual( + "ANY " + #keyPath(TestEntity1.testToOne.testToManyUnordered), + (\TestEntity1.testToOne ~ \.testToManyUnordered).any().description + ) + XCTAssertEqual( + "ANY " + #keyPath(TestEntity2.testToOne.testToOne.testToManyOrdered), + (\TestEntity2.testToOne ~ \.testToOne ~ \.testToManyOrdered).any().description + ) + } + do { + + XCTAssertEqual( + "ANY master.pets", + (\Animal.master ~ \.pets).any().description + ) + XCTAssertEqual( + "ANY master.pets.species", + (\Animal.master ~ \.pets ~ \.species).any().description + ) + } + do { + + XCTAssertEqual( + "ALL " + #keyPath(TestEntity1.testToOne.testToManyUnordered), + (\TestEntity1.testToOne ~ \.testToManyUnordered).all().description + ) + XCTAssertEqual( + "ALL " + #keyPath(TestEntity2.testToOne.testToOne.testToManyOrdered), + (\TestEntity2.testToOne ~ \.testToOne ~ \.testToManyOrdered).all().description + ) + } + do { + + XCTAssertEqual( + "ALL master.pets", + (\Animal.master ~ \.pets).all().description + ) + XCTAssertEqual( + "ALL master.pets.species", + (\Animal.master ~ \.pets ~ \.species).all().description + ) + } + do { + + XCTAssertEqual( + "NONE " + #keyPath(TestEntity1.testToOne.testToManyUnordered), + (\TestEntity1.testToOne ~ \.testToManyUnordered).none().description + ) + XCTAssertEqual( + "NONE " + #keyPath(TestEntity2.testToOne.testToOne.testToManyOrdered), + (\TestEntity2.testToOne ~ \.testToOne ~ \.testToManyOrdered).none().description + ) + } + do { + + XCTAssertEqual( + "NONE master.pets", + (\Animal.master ~ \.pets).none().description + ) + XCTAssertEqual( + "NONE master.pets.species", + (\Animal.master ~ \.pets ~ \.species).none().description + ) + } + } + + @objc + dynamic func test_ThatWhereClauses_CanBeCreatedFromExpressionsCorrectly() { + + do { + + let dummy = "dummy" + let whereClause: Where = (\.testToOne ~ \.testString) == dummy + let predicate = NSPredicate(format: "\(#keyPath(TestEntity1.testToOne.testString)) == %@", dummy) + XCTAssertEqual(whereClause, Where(predicate)) + XCTAssertEqual(whereClause.predicate, predicate) + } + do { + + let dummy = "dummy" + let whereClause: Where = (\.testToOne ~ \.testToOne ~ \.testString) == dummy + let predicate = NSPredicate(format: "\(#keyPath(TestEntity1.testToOne.testToOne.testString)) == %@", dummy) + XCTAssertEqual(whereClause, Where(predicate)) + XCTAssertEqual(whereClause.predicate, predicate) + } + do { + + let count = 3 + let whereClause: Where = (\.testToOne ~ \.testToManyUnordered).count() == count + let predicate = NSPredicate(format: "\(#keyPath(TestEntity1.testToOne.testToManyUnordered)).@count == %d", count) + XCTAssertEqual(whereClause, Where(predicate)) + XCTAssertEqual(whereClause.predicate, predicate) + } + do { + + let dummy = "dummy" + let whereClause: Where = (\.testToOne ~ \.testToManyUnordered ~ \TestEntity1.testString).any() == dummy + let predicate = NSPredicate(format: "ANY \(#keyPath(TestEntity1.testToOne.testToManyUnordered)).\(#keyPath(TestEntity1.testString)) == %@", dummy) + XCTAssertEqual(whereClause, Where(predicate)) + XCTAssertEqual(whereClause.predicate, predicate) + } } @objc diff --git a/Sources/CoreStore+CustomDebugStringConvertible.swift b/Sources/CoreStore+CustomDebugStringConvertible.swift index 01344ad..90eb223 100644 --- a/Sources/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/CoreStore+CustomDebugStringConvertible.swift @@ -1232,6 +1232,8 @@ extension NSAttributeType: CoreStoreDebugStringConvertible { case .objectIDAttributeType: return ".objectIDAttributeType" case .UUIDAttributeType: return ".UUIDAttributeType" case .URIAttributeType: return ".URIAttributeType" + @unknown default: + fatalError() } } } @@ -1254,6 +1256,8 @@ extension NSDeleteRule: CoreStoreDebugStringConvertible { case .nullifyDeleteRule: return ".nullifyDeleteRule" case .cascadeDeleteRule: return ".cascadeDeleteRule" case .denyDeleteRule: return ".denyDeleteRule" + @unknown default: + fatalError() } } } diff --git a/Sources/CoreStore+Logging.swift b/Sources/CoreStore+Logging.swift index 0e13dd9..9eb48e9 100644 --- a/Sources/CoreStore+Logging.swift +++ b/Sources/CoreStore+Logging.swift @@ -66,8 +66,8 @@ extension CoreStore { internal static func assert( _ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) { self.logger.assert( - condition, - message: message, + condition(), + message: message(), fileName: fileName, lineNumber: lineNumber, functionName: functionName diff --git a/Sources/DynamicKeyPath.swift b/Sources/DynamicKeyPath.swift index a83034d..f094a1f 100644 --- a/Sources/DynamicKeyPath.swift +++ b/Sources/DynamicKeyPath.swift @@ -47,7 +47,7 @@ public protocol DynamicKeyPath: AnyDynamicKeyPath { /** The DynamicObject type */ - associatedtype ObjectType + associatedtype ObjectType: DynamicObject /** The Value type @@ -66,7 +66,7 @@ extension KeyPathString { let keyPath = String(keyPath: \Person.nickname) ``` */ - public init(keyPath: KeyPath) { + public init(keyPath: KeyPath) { self = keyPath.cs_keyPathString } @@ -81,18 +81,24 @@ extension KeyPathString { self = O.meta[keyPath: keyPath].cs_keyPathString } + + /** + Extracts the keyPath string from the property. + ``` + let keyPath = String(keyPath: \Person.nickname) + ``` + */ + public init(keyPath: Where.Expression) { + + self = keyPath.cs_keyPathString + } } + // MARK: - KeyPath: DynamicKeyPath -// TODO: SE-0143 (https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md) is implemented but multiple conformances for the same type currently cannot be declared. -//extension KeyPath: DynamicKeyPath where Root: NSManagedObject, Value: ImportableAttributeType -//extension KeyPath: DynamicKeyPath where Root: NSManagedObject, Value: ImportableAttributeType? -//extension KeyPath: DynamicKeyPath where Root: NSManagedObject, Value: NSManagedObject? -//extension KeyPath: DynamicKeyPath where Root: NSManagedObject, Value: NSSet -//extension KeyPath: DynamicKeyPath where Root: NSManagedObject, Value: NSOrderedSet -extension KeyPath: DynamicKeyPath { +extension KeyPath: DynamicKeyPath, AnyDynamicKeyPath where Root: DynamicObject, Value: AllowedObjectiveCKeyPathValue { public typealias ObjectType = Root public typealias ValueType = Value diff --git a/Sources/KeyPathGenericBindings.swift b/Sources/KeyPathGenericBindings.swift new file mode 100644 index 0000000..72cbb60 --- /dev/null +++ b/Sources/KeyPathGenericBindings.swift @@ -0,0 +1,140 @@ +// +// KeyPathGenericBindings.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 CoreGraphics +import CoreData + + +// MARK: - AllowedObjectiveCKeyPathValue + +/** + Used only for utility methods. Types allowed as `Value` generic type to `KeyPath` utilities. + */ +public protocol AllowedObjectiveCKeyPathValue {} + + +// MARK: - AllowedOptionalObjectiveCKeyPathValue + +/** + Used only for utility methods. Types allowed as `Value` generic type to `KeyPath` utilities. + */ +public protocol AllowedOptionalObjectiveCKeyPathValue: AllowedObjectiveCKeyPathValue {} + +extension Bool: AllowedObjectiveCKeyPathValue {} + +extension CGFloat: AllowedObjectiveCKeyPathValue {} + +extension Data: AllowedOptionalObjectiveCKeyPathValue {} + +extension Date: AllowedOptionalObjectiveCKeyPathValue {} + +extension Double: AllowedObjectiveCKeyPathValue {} + +extension Float: AllowedObjectiveCKeyPathValue {} + +extension Int: AllowedObjectiveCKeyPathValue {} + +extension Int8: AllowedObjectiveCKeyPathValue {} + +extension Int16: AllowedObjectiveCKeyPathValue {} + +extension Int32: AllowedObjectiveCKeyPathValue {} + +extension Int64: AllowedObjectiveCKeyPathValue {} + +extension NSData: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSDate: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSManagedObject: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSNumber: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSString: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSSet: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSOrderedSet: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSURL: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSUUID: AllowedOptionalObjectiveCKeyPathValue {} + +extension String: AllowedOptionalObjectiveCKeyPathValue {} + +extension URL: AllowedOptionalObjectiveCKeyPathValue {} + +extension UUID: AllowedOptionalObjectiveCKeyPathValue {} + +extension Optional: AllowedObjectiveCKeyPathValue where Wrapped: AllowedOptionalObjectiveCKeyPathValue {} + + +// MARK: - AllowedObjectiveCCollectionKeyPathValue + +/** + Used only for utility methods. Types allowed as `Value` generic type to `KeyPath` utilities. + */ +public protocol AllowedObjectiveCCollectionKeyPathValue: AllowedOptionalObjectiveCKeyPathValue {} + +extension NSSet: AllowedObjectiveCCollectionKeyPathValue {} + +extension NSOrderedSet: AllowedObjectiveCCollectionKeyPathValue {} + +extension Optional: AllowedObjectiveCCollectionKeyPathValue, AllowedOptionalObjectiveCKeyPathValue where Wrapped: AllowedObjectiveCCollectionKeyPathValue {} + + +// MARK: - AllowedCoreStoreObjectKeyPathValue + +/** + Used only for utility methods. Types allowed as `Value` generic type to `KeyPath` utilities. + */ +public protocol AllowedCoreStoreObjectKeyPathValue: DynamicKeyPath {} + +extension ValueContainer.Required: AllowedCoreStoreObjectKeyPathValue {} + +extension ValueContainer.Optional: AllowedCoreStoreObjectKeyPathValue {} + +extension TransformableContainer.Required: AllowedCoreStoreObjectKeyPathValue {} + +extension TransformableContainer.Optional: AllowedCoreStoreObjectKeyPathValue {} + +extension RelationshipContainer.ToOne: AllowedCoreStoreObjectKeyPathValue {} + +extension RelationshipContainer.ToManyOrdered: AllowedCoreStoreObjectKeyPathValue {} + +extension RelationshipContainer.ToManyUnordered: AllowedCoreStoreObjectKeyPathValue {} + + +// MARK: - AllowedCoreStoreObjectCollectionKeyPathValue + +/** + Used only for utility methods. Types allowed as `Value` generic type to `KeyPath` utilities. + */ +public protocol AllowedCoreStoreObjectCollectionKeyPathValue: AllowedCoreStoreObjectKeyPathValue {} + +extension RelationshipContainer.ToManyOrdered: AllowedCoreStoreObjectCollectionKeyPathValue {} + +extension RelationshipContainer.ToManyUnordered: AllowedCoreStoreObjectCollectionKeyPathValue {} diff --git a/Sources/ListMonitor.swift b/Sources/ListMonitor.swift index 4278cdf..06c9abd 100644 --- a/Sources/ListMonitor.swift +++ b/Sources/ListMonitor.swift @@ -1390,6 +1390,9 @@ extension ListMonitor: FetchedResultsControllerHandler { "\(String(describing: IndexPath.self)).New": newIndexPath! ] ) + + @unknown default: + fatalError() } } diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index ed1c446..62fefde 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -105,9 +105,9 @@ public enum RelationshipContainer { keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -140,9 +140,9 @@ public enum RelationshipContainer { keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -175,9 +175,9 @@ public enum RelationshipContainer { keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -210,9 +210,9 @@ public enum RelationshipContainer { keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -354,9 +354,9 @@ public enum RelationshipContainer { maxCount: maxCount, inverseKeyPath: { nil }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -395,9 +395,9 @@ public enum RelationshipContainer { maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -436,9 +436,9 @@ public enum RelationshipContainer { maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -477,9 +477,9 @@ public enum RelationshipContainer { maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -627,9 +627,9 @@ public enum RelationshipContainer { deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -668,9 +668,9 @@ public enum RelationshipContainer { deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -709,9 +709,9 @@ public enum RelationshipContainer { deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } @@ -750,9 +750,9 @@ public enum RelationshipContainer { deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, - versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, - affectedByKeyPaths: affectedByKeyPaths + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() ) } diff --git a/Sources/Where.Expression.swift b/Sources/Where.Expression.swift new file mode 100644 index 0000000..c31f92b --- /dev/null +++ b/Sources/Where.Expression.swift @@ -0,0 +1,304 @@ +// +// 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: - ~ + +infix operator ~ : AdditionPrecedence + + +// MARK: - WhereExpressionTrait + +public protocol WhereExpressionTrait {} + + +// MARK: - Where + +extension Where { + + // MARK: - Expression + + public struct Expression: CustomStringConvertible, DynamicKeyPath { + + public typealias Trait = T + + + // MARK: AnyDynamicKeyPath + + public let cs_keyPathString: String + + + // MARK: DynamicKeyPath + + public typealias ObjectType = D + public typealias ValueType = 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 + + public enum SingleTarget: WhereExpressionTrait {} + + // MARK: - CollectionTarget + + public enum CollectionTarget: WhereExpressionTrait {} +} + + +// MARK: - ~ Operator (Where.Expression Creation Operators) + +// MARK: ~ where D: NSManagedObject + +public func ~(_ lhs: KeyPath, _ rhs: KeyPath) -> Where.Expression.SingleTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: KeyPath, _ rhs: KeyPath) -> Where.Expression.SingleTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: KeyPath, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: KeyPath, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, V> { + + return .init(lhs.cs_keyPathString, rhs.cs_keyPathString) +} + + +// MARK: - ~ where D: CoreStoreObject + +public func ~(_ lhs: KeyPath.ToOne>, _ rhs: KeyPath) -> Where.Expression.SingleTarget, K.ValueType> where K.ObjectType == O { + + return .init( + D.meta[keyPath: lhs].cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + +public func ~(_ lhs: KeyPath.ToOne>, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.ValueType> where K.ObjectType == O { + + return .init( + D.meta[keyPath: lhs].cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression where K.ObjectType == O { + + return .init( + lhs.cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.ValueType> where K.ObjectType == O { + + return .init( + lhs.cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression where K.ObjectType == O { + + return .init( + lhs.cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, K.ValueType> where K.ObjectType == O { + + return .init( + lhs.cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + +public func ~(_ lhs: Where.Expression, _ rhs: KeyPath) -> Where.Expression.CollectionTarget, KV.ValueType> where KC.ObjectType == D, KV.ObjectType == O { + + return .init( + lhs.cs_keyPathString, + O.meta[keyPath: rhs].cs_keyPathString + ) +} + + +// MARK: - Where.Expression where V: QueryableAttributeType + +public func == (_ lhs: Where.Expression, _ rhs: V) -> Where { + + return Where(lhs.cs_keyPathString, isEqualTo: rhs) +} + +public func != (_ lhs: Where.Expression, _ rhs: V) -> Where { + + return !Where(lhs.cs_keyPathString, isEqualTo: rhs) +} + +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 + +public func == (_ lhs: Where.Expression, _ rhs: V) -> Where { + + return Where(lhs.cs_keyPathString, isEqualTo: rhs) +} + +public func == (_ lhs: Where.Expression, _ rhs: V?) -> Where { + + return Where(lhs.cs_keyPathString, isEqualTo: rhs) +} + +public func != (_ lhs: Where.Expression, _ rhs: V) -> Where { + + return !Where(lhs.cs_keyPathString, isEqualTo: rhs) +} + +public func != (_ lhs: Where.Expression, _ rhs: V?) -> Where { + + return !Where(lhs.cs_keyPathString, isEqualTo: rhs) +} + +public func ~= (_ sequence: S, _ expression: Where.Expression) -> Where where S.Iterator.Element == V { + + return Where(expression.cs_keyPathString, isMemberOf: sequence) +} + + +// MARK: - Where.Expression where D: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCCollectionKeyPathValue + +extension Where.Expression where D: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCCollectionKeyPathValue { + + public func count() -> Where.Expression.CollectionTarget, Int> { + + return .init(self.cs_keyPathString, "@count") + } +} + + +// MARK: - Where.Expression where D: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCKeyPathValue + +extension Where.Expression where D: NSManagedObject, T == Where.CollectionTarget, V: AllowedObjectiveCKeyPathValue { + + public func any() -> Where.Expression.CollectionTarget, V> { + + return .init("ANY " + self.cs_keyPathString) + } + + public func all() -> Where.Expression.CollectionTarget, V> { + + return .init("ALL " + self.cs_keyPathString) + } + + public func none() -> Where.Expression.CollectionTarget, V> { + + return .init("NONE " + self.cs_keyPathString) + } +} + + +// MARK: - Where.Expression where D: CoreStoreObject, T == Where.CollectionTarget + +extension Where.Expression where D: CoreStoreObject, T == Where.CollectionTarget { + + public func count() -> Where.Expression.CollectionTarget, Int> { + + return .init(self.cs_keyPathString, "@count") + } + + public func any() -> Where.Expression.CollectionTarget, V> { + + return .init("ANY " + self.cs_keyPathString) + } + + public func all() -> Where.Expression.CollectionTarget, V> { + + return .init("ALL " + self.cs_keyPathString) + } + + public func none() -> Where.Expression.CollectionTarget, V> { + + return .init("NONE " + self.cs_keyPathString) + } +} diff --git a/Sources/WhereClauseType.swift b/Sources/WhereClauseType.swift index b55468f..489435c 100644 --- a/Sources/WhereClauseType.swift +++ b/Sources/WhereClauseType.swift @@ -39,41 +39,41 @@ public protocol WhereClauseType: AnyWhereClause { associatedtype ObjectType: DynamicObject } -extension WhereClauseType where Self.ObjectType: CoreStoreObject { +extension WhereClauseType { /** Combines two `Where` predicates together using `AND` operator. - Warning: This operator overload is a workaround for Swift generics' inability to constrain by inheritance (https://bugs.swift.org/browse/SR-5213). In effect, this is less type-safe than other overloads because it allows AND'ing clauses of unrelated `DynamicObject` types. */ - public static func && (left: Self, right: TWhere) -> Self { - - return Self.init(NSCompoundPredicate(type: .and, subpredicates: [left.predicate, right.predicate])) + public static func && (left: Self, right: TWhere) -> Where { + + return .init(NSCompoundPredicate(type: .and, subpredicates: [left.predicate, right.predicate])) } - + /** Combines two `Where` predicates together using `AND` operator. - Warning: This operator overload is a workaround for Swift generics' inability to constrain by inheritance (https://bugs.swift.org/browse/SR-5213). In effect, this is less type-safe than other overloads because it allows AND'ing clauses of unrelated `DynamicObject` types. */ - public static func && (left: TWhere, right: Self) -> Self { - - return Self.init(NSCompoundPredicate(type: .and, subpredicates: [left.predicate, right.predicate])) + public static func && (left: TWhere, right: Self) -> Where { + + return .init(NSCompoundPredicate(type: .and, subpredicates: [left.predicate, right.predicate])) } - + /** Combines two `Where` predicates together using `OR` operator. - Warning: This operator overload is a workaround for Swift generics' inability to constrain by inheritance (https://bugs.swift.org/browse/SR-5213). In effect, this is less type-safe than other overloads because it allows OR'ing clauses of unrelated `DynamicObject` types. */ - public static func || (left: Self, right: TWhere) -> Self { - - return Self.init(NSCompoundPredicate(type: .or, subpredicates: [left.predicate, right.predicate])) + public static func || (left: Self, right: TWhere) -> Where { + + return .init(NSCompoundPredicate(type: .or, subpredicates: [left.predicate, right.predicate])) } - + /** Combines two `Where` predicates together using `OR` operator. - Warning: This operator overload is a workaround for Swift generics' inability to constrain by inheritance (https://bugs.swift.org/browse/SR-5213). In effect, this is less type-safe than other overloads because it allows OR'ing clauses of unrelated `DynamicObject` types. */ - public static func || (left: TWhere, right: Self) -> Self { - - return Self.init(NSCompoundPredicate(type: .or, subpredicates: [left.predicate, right.predicate])) + public static func || (left: TWhere, right: Self) -> Where { + + return .init(NSCompoundPredicate(type: .or, subpredicates: [left.predicate, right.predicate])) } }