From 92ad89504494d758c8a86c656b3ec5e23525d052 Mon Sep 17 00:00:00 2001 From: John Estropia Date: Mon, 20 Jan 2020 17:13:01 +0900 Subject: [PATCH] Field.Relationship propertyWrapper --- CoreStore.xcodeproj/project.pbxproj | 30 ++ CoreStoreTests/DynamicModelTests.swift | 133 ++++----- CoreStoreTests/WhereTests.swift | 52 ++-- Sources/CoreStoreSchema.swift | 37 +++ Sources/DynamicObject.swift | 3 + Sources/FIeldRelationshipType.swift | 142 ++++++++++ Sources/Field.Relationship.swift | 346 ++++++++++++++++++++++++ Sources/FieldRelationshipProtocol.swift | 49 ++++ Sources/KeyPath+Querying.swift | 121 +++++++++ Sources/Select.swift | 233 ++-------------- Sources/Where.Expression.swift | 28 ++ Sources/Where.swift | 11 + 12 files changed, 881 insertions(+), 304 deletions(-) create mode 100644 Sources/FIeldRelationshipType.swift create mode 100644 Sources/Field.Relationship.swift create mode 100644 Sources/FieldRelationshipProtocol.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index a1267e9..9d27086 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -613,6 +613,18 @@ B57D27C21D0BC20100539C58 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27C11D0BC20100539C58 /* QueryTests.swift */; }; B57D27C31D0BC20100539C58 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27C11D0BC20100539C58 /* QueryTests.swift */; }; B57D27C41D0BC20100539C58 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27C11D0BC20100539C58 /* QueryTests.swift */; }; + B57E6FA223D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; }; + B57E6FA323D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; }; + B57E6FA423D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; }; + B57E6FA523D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; }; + B57E6FA723D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; }; + B57E6FA823D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; }; + B57E6FA923D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; }; + B57E6FAA23D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; }; + B57E6FAC23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; }; + B57E6FAD23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; }; + B57E6FAE23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; }; + B57E6FAF23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; }; B580857A1CDF808C004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; @@ -1113,6 +1125,9 @@ B56E4EE323CEDF0900E1708C /* Field.Computed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Computed.swift; sourceTree = ""; }; B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = ""; }; B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; + B57E6FA123D302FA000FD031 /* Field.Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Relationship.swift; sourceTree = ""; }; + B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FIeldRelationshipType.swift; sourceTree = ""; }; + B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldRelationshipProtocol.swift; sourceTree = ""; }; B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = ""; }; B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisherTests.swift; sourceTree = ""; }; B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeProtocol.swift; sourceTree = ""; }; @@ -1461,6 +1476,7 @@ B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */, B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */, B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */, + B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */, B50564D22350CC3100482308 /* PropertyProtocol.swift */, B53D9E5823513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift */, B50E175623517DE4004F033C /* Differentiable.swift */, @@ -1608,6 +1624,7 @@ B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */, B56E4EE323CEDF0900E1708C /* Field.Computed.swift */, B50C3EE423D153EA00B29880 /* Field.Coded.swift */, + B57E6FA123D302FA000FD031 /* Field.Relationship.swift */, B50C3EDE23D05BB200B29880 /* Coders */, B56E4EDD23CEBB0400E1708C /* Supported Values */, ); @@ -1619,6 +1636,7 @@ children = ( B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */, B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */, + B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */, ); name = "Supported Values"; sourceTree = ""; @@ -2301,6 +2319,7 @@ B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, B5831B751F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, + B57E6FA723D305D6000FD031 /* FIeldRelationshipType.swift in Sources */, B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */, B50564D32350CC3100482308 /* PropertyProtocol.swift in Sources */, B5D8CA762346E7590055D7D1 /* DataStack+DataSources.swift in Sources */, @@ -2435,6 +2454,7 @@ B5A991EC1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5FE4DA71C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B57E6FA223D302FA000FD031 /* Field.Relationship.swift in Sources */, B5E8A72021C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */, B53D9E5923513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift in Sources */, B56E4ED923CEB8E700E1708C /* FieldStorableType.swift in Sources */, @@ -2446,6 +2466,7 @@ B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */, + B57E6FAC23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */, B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, B50C3EEF23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, @@ -2549,6 +2570,7 @@ B53FBA141CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, B5831B761F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, B5BF7FB3234C97910070E741 /* DiffableDataSource.swift in Sources */, + B57E6FA823D305D6000FD031 /* FIeldRelationshipType.swift in Sources */, B5E1B5AA1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */, B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, B5E1B59F1CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */, @@ -2683,6 +2705,7 @@ B5AA37F2235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */, B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */, B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, + B57E6FA323D302FA000FD031 /* Field.Relationship.swift in Sources */, B501322B2346A9AE00FC238B /* ListPublisher.swift in Sources */, B56924001EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */, B56E4EDA23CEB8E700E1708C /* FieldStorableType.swift in Sources */, @@ -2694,6 +2717,7 @@ 82BA18BE1C4BBD4A00A0916E /* Tweak.swift in Sources */, B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, + B57E6FAD23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */, B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B50C3EF023D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, @@ -2797,6 +2821,7 @@ B5BF7FCE234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */, B52DD19E1BE1F92C00949AFE /* AsynchronousDataTransaction.swift in Sources */, B50564D62350CC3100482308 /* PropertyProtocol.swift in Sources */, + B57E6FAA23D305D6000FD031 /* FIeldRelationshipType.swift in Sources */, B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */, B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, @@ -2931,6 +2956,7 @@ 18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */, B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */, B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */, + B57E6FA523D302FA000FD031 /* Field.Relationship.swift in Sources */, B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, B501322E2346A9B100FC238B /* ListPublisher.swift in Sources */, B56E4EDC23CEB8E700E1708C /* FieldStorableType.swift in Sources */, @@ -2942,6 +2968,7 @@ B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */, B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5519A5C1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */, + B57E6FAF23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */, B5DBE2D51C991B3E00B5CEFA /* CSDataStack.swift in Sources */, B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B50C3EF223D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, @@ -3045,6 +3072,7 @@ B5E1B5AB1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */, B5831B771F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, B5BF7FB4234C97910070E741 /* DiffableDataSource.swift in Sources */, + B57E6FA923D305D6000FD031 /* FIeldRelationshipType.swift in Sources */, B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, B5E1B5A01CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */, B5ECDC261CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */, @@ -3179,6 +3207,7 @@ B5AA37F3235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */, B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */, B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, + B57E6FA423D302FA000FD031 /* Field.Relationship.swift in Sources */, B501322D2346A9B000FC238B /* ListPublisher.swift in Sources */, B56924011EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */, B56E4EDB23CEB8E700E1708C /* FieldStorableType.swift in Sources */, @@ -3190,6 +3219,7 @@ B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */, B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, + B57E6FAE23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */, B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B50C3EF123D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index 7cd1362..eba30a4 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -44,7 +44,8 @@ class Animal: CoreStoreObject { @Field.Coded("color", coder: FieldCoders.NSCoding.self) var color: Color? = .blue - let master = Relationship.ToOne("master") + @Field.Relationship("master") + var master: Person? } class Dog: Animal { @@ -52,15 +53,45 @@ class Dog: Animal { @Field.Stored("nickname") var nickname: String? - let age = Value.Required("age", initial: 1) - let friends = Relationship.ToManyOrdered("friends") - let friendedBy = Relationship.ToManyUnordered("friendedBy", inverse: { $0.friends }) + @Field.Stored("age") + var age: Int = 1 + + @Field.Relationship("friends") + var friends: [Dog] + + @Field.Relationship("friendedBy", inverse: \.$friends) + var friendedBy: Set } struct CustomType { var string = "customString" } +enum Job: String { + + case unemployed + case engineer + case doctor + case lawyer + + init?(data: Data) { + + guard + let rawValue = String(data: data, encoding: .utf8), + let value = Self.init(rawValue: rawValue) + else { + + return nil + } + self = value + } + + func toData() -> Data { + + return Data(self.rawValue.utf8) + } +} + class Person: CoreStoreObject { @Field.Stored("title", customSetter: Person.setTitle(_:_:)) @@ -84,47 +115,25 @@ class Person: CoreStoreObject { @Field.Coded( "job", coder: ( - encode: { $0.data }, + encode: { $0.toData() }, decode: { $0.flatMap(Job.init(data:)) ?? .unemployed } ) ) var job: Job = .unemployed - let spouse = Relationship.ToOne("spouse") - - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + @Field.Relationship("spouse") + var spouse: Person? - private let _spouse = Relationship.ToOne("_spouseInverse", inverse: { $0.spouse }) + @Field.Relationship("pets", inverse: \.$master) + var pets: Set - enum Job: String { - - case unemployed - case engineer - case doctor - case lawyer - - init?(data: Data) { - - guard - let rawValue = String(data: data, encoding: .utf8), - let value = Self.init(rawValue: rawValue) - else { - - return nil - } - self = value - } - - var data: Data { - - return Data(self.rawValue.utf8) - } - } + @Field.Relationship("_spouseInverse", inverse: \.$spouse) + private var spouseInverse: Person? private static func setTitle(_ partialObject: PartialObject, _ newValue: String) { partialObject.setPrimitiveValue(newValue, for: \.$title) - partialObject.setPrimitiveValue(nil, for: { $0.$displayName }) + partialObject.setPrimitiveValue(nil, for: \.$displayName) } private static func setName(_ partialObject: PartialObject, _ newValue: String) { @@ -226,8 +235,8 @@ class DynamicModelTests: BaseTestDataTestCase { case String(keyPath: \Animal.$species): XCTAssertTrue(property is FieldContainer.Stored) - case String(keyPath: \Animal.master): - XCTAssertTrue(property is RelationshipContainer.ToOne) + case String(keyPath: \Animal.$master): + XCTAssertTrue(property is FieldContainer.Relationship) case String(keyPath: \Animal.$color): XCTAssertTrue(property is FieldContainer.Coded) @@ -240,7 +249,7 @@ class DynamicModelTests: BaseTestDataTestCase { let dog = transaction.create(Into()) XCTAssertEqual(dog.species, "Swift") XCTAssertEqual(dog.nickname, nil) - XCTAssertEqual(dog.age.value, 1) + XCTAssertEqual(dog.age, 1) for property in Dog.metaProperties(includeSuperclasses: true) { @@ -249,8 +258,8 @@ class DynamicModelTests: BaseTestDataTestCase { case String(keyPath: \Dog.$species): XCTAssertTrue(property is FieldContainer.Stored) - case String(keyPath: \Dog.master): - XCTAssertTrue(property is RelationshipContainer.ToOne) + case String(keyPath: \Dog.$master): + XCTAssertTrue(property is FieldContainer.Relationship) case String(keyPath: \Dog.$color): XCTAssertTrue(property is FieldContainer.Coded) @@ -258,14 +267,14 @@ class DynamicModelTests: BaseTestDataTestCase { case String(keyPath: \Dog.$nickname): XCTAssertTrue(property is FieldContainer.Stored) - case String(keyPath: \Dog.age): - XCTAssertTrue(property is ValueContainer.Required) + case String(keyPath: \Dog.$age): + XCTAssertTrue(property is FieldContainer.Stored) - case String(keyPath: \Dog.friends): - XCTAssertTrue(property is RelationshipContainer.ToManyOrdered) + case String(keyPath: \Dog.$friends): + XCTAssertTrue(property is FieldContainer.Relationship<[Dog]>) - case String(keyPath: \Dog.friendedBy): - XCTAssertTrue(property is RelationshipContainer.ToManyUnordered) + case String(keyPath: \Dog.$friendedBy): + XCTAssertTrue(property is FieldContainer.Relationship>) default: XCTFail("Unknown KeyPath: \"\(property.keyPath)\"") @@ -325,7 +334,7 @@ class DynamicModelTests: BaseTestDataTestCase { XCTAssertEqual(dog.nickname, "Spot") let person = transaction.create(Into()) - XCTAssertTrue(person.pets.value.isEmpty) + XCTAssertTrue(person.pets.isEmpty) XCTAssertEqual(person.customField.string, "customString") XCTAssertEqual(person.job, .unemployed) @@ -382,12 +391,12 @@ class DynamicModelTests: BaseTestDataTestCase { - person.pets.value.insert(dog) + person.pets.insert(dog) XCTAssertEqual(person.pets.count, 1) - XCTAssertEqual(person.pets.value.first, dog) - XCTAssertEqual(person.pets.value.first?.master.value, person) - XCTAssertEqual(dog.master.value, person) - XCTAssertEqual(dog.master.value?.pets.value.first, dog) + XCTAssertEqual(person.pets.first, dog) + XCTAssertEqual(person.pets.first?.master, person) + XCTAssertEqual(dog.master, person) + XCTAssertEqual(dog.master?.pets.first, dog) }, success: { _ in @@ -424,42 +433,44 @@ class DynamicModelTests: BaseTestDataTestCase { XCTAssertEqual(person!.displayName, "Sir John") XCTAssertEqual(person!.customField.string, "customString") XCTAssertEqual(person!.job, .engineer) - XCTAssertEqual(person!.pets.value.first, dog) + XCTAssertEqual(person!.pets.first, dog) - let p3 = Where({ $0.age == 10 }) + let p3 = Where({ $0.$age == 10 }) XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10)) - let totalAge = try transaction.queryValue(From().select(Int.self, .sum(\Dog.age))) + let totalAge = try transaction.queryValue( + From().select(Int.self, .sum(\.$age)) + ) XCTAssertEqual(totalAge, 1) _ = try transaction.fetchAll( From() - .where(\Animal.$species == "Dog" && \Dog.age == 10) + .where(\Animal.$species == "Dog" && \Dog.$age == 10) ) _ = try transaction.fetchAll( From() - .where(\Dog.age == 10 && \Animal.$species == "Dog") + .where(\Dog.$age == 10 && \Animal.$species == "Dog") .orderBy(.ascending({ $0.$species })) ) _ = try transaction.fetchAll( From(), - Where({ $0.age > 10 && $0.age <= 15 }) + Where({ $0.$age > 10 && $0.$age <= 15 }) ) _ = try transaction.fetchAll( From(), - Where({ $0.$species == "Dog" && $0.age == 10 }) + Where({ $0.$species == "Dog" && $0.$age == 10 }) ) _ = try transaction.fetchAll( From(), - Where({ $0.age == 10 && $0.$species == "Dog" }) + Where({ $0.$age == 10 && $0.$species == "Dog" }) ) _ = try transaction.fetchAll( From(), - Where({ $0.age > 10 && $0.age <= 15 }) + Where({ $0.$age > 10 && $0.$age <= 15 }) ) _ = try transaction.fetchAll( From(), - (\Dog.age > 10 && \Dog.age <= 15) + (\Dog.$age > 10 && \Dog.$age <= 15) ) }, success: { _ in diff --git a/CoreStoreTests/WhereTests.swift b/CoreStoreTests/WhereTests.swift index 1dbff23..6dcaed7 100644 --- a/CoreStoreTests/WhereTests.swift +++ b/CoreStoreTests/WhereTests.swift @@ -104,18 +104,18 @@ final class WhereTests: XCTestCase { XCTAssertAllEqual( "master.pets", - (\Animal.master ~ \.pets).description, - String(keyPath: \Animal.master ~ \.pets) + (\Animal.$master ~ \.$pets).description, + String(keyPath: \Animal.$master ~ \.$pets) ) XCTAssertAllEqual( "master.pets.species", - (\Animal.master ~ \.pets ~ \.$species).description, - String(keyPath: \Animal.master ~ \.pets ~ \.$species) + (\Animal.$master ~ \.$pets ~ \.$species).description, + String(keyPath: \Animal.$master ~ \.$pets ~ \.$species) ) XCTAssertAllEqual( "master.pets.master", - (\Animal.master ~ \.pets ~ \.master).description, - String(keyPath: \Animal.master ~ \.pets ~ \.master) + (\Animal.$master ~ \.$pets ~ \.$master).description, + String(keyPath: \Animal.$master ~ \.$pets ~ \.$master) ) } } @@ -138,8 +138,8 @@ final class WhereTests: XCTestCase { XCTAssertAllEqual( "master.pets.@count", - (\Animal.master ~ \.pets).count().description, - String(keyPath: (\Animal.master ~ \.pets).count()) + (\Animal.$master ~ \.$pets).count().description, + String(keyPath: (\Animal.$master ~ \.$pets).count()) ) } } @@ -162,13 +162,13 @@ final class WhereTests: XCTestCase { XCTAssertAllEqual( "ANY master.pets", - (\Animal.master ~ \.pets).any().description, - String(keyPath: (\Animal.master ~ \.pets).any()) + (\Animal.$master ~ \.$pets).any().description, + String(keyPath: (\Animal.$master ~ \.$pets).any()) ) XCTAssertAllEqual( "ANY master.pets.species", - (\Animal.master ~ \.pets ~ \.$species).any().description, - String(keyPath: (\Animal.master ~ \.pets ~ \.$species).any()) + (\Animal.$master ~ \.$pets ~ \.$species).any().description, + String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).any()) ) } } @@ -191,13 +191,13 @@ final class WhereTests: XCTestCase { XCTAssertAllEqual( "ALL master.pets", - (\Animal.master ~ \.pets).all().description, - String(keyPath: (\Animal.master ~ \.pets).all()) + (\Animal.$master ~ \.$pets).all().description, + String(keyPath: (\Animal.$master ~ \.$pets).all()) ) XCTAssertAllEqual( "ALL master.pets.species", - (\Animal.master ~ \.pets ~ \.$species).all().description, - String(keyPath: (\Animal.master ~ \.pets ~ \.$species).all()) + (\Animal.$master ~ \.$pets ~ \.$species).all().description, + String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).all()) ) } } @@ -220,13 +220,13 @@ final class WhereTests: XCTestCase { XCTAssertAllEqual( "NONE master.pets", - (\Animal.master ~ \.pets).none().description, - String(keyPath: (\Animal.master ~ \.pets).none()) + (\Animal.$master ~ \.$pets).none().description, + String(keyPath: (\Animal.$master ~ \.$pets).none()) ) XCTAssertAllEqual( "NONE master.pets.species", - (\Animal.master ~ \.pets ~ \.$species).none().description, - String(keyPath: (\Animal.master ~ \.pets ~ \.$species).none()) + (\Animal.$master ~ \.$pets ~ \.$species).none().description, + String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).none()) ) } } @@ -247,7 +247,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.$name) == dummy + let whereClause: Where = (\.$master ~ \.$name) == dummy let predicate = NSPredicate(format: "master.name == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -265,7 +265,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.spouse ~ \.$name) == dummy + let whereClause: Where = (\.$master ~ \.$spouse ~ \.$name) == dummy let predicate = NSPredicate(format: "master.spouse.name == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -283,7 +283,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets).count() == count + let whereClause: Where = (\.$master ~ \.$pets).count() == count let predicate = NSPredicate(format: "master.pets.@count == %d", count) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -301,7 +301,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets ~ \.$species).any() == dummy + let whereClause: Where = (\.$master ~ \.$pets ~ \.$species).any() == dummy let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -319,7 +319,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets ~ \.$species).all() == dummy + let whereClause: Where = (\.$master ~ \.$pets ~ \.$species).all() == dummy let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -337,7 +337,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets ~ \.$species).none() == dummy + let whereClause: Where = (\.$master ~ \.$pets ~ \.$species).none() == dummy let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) diff --git a/Sources/CoreStoreSchema.swift b/Sources/CoreStoreSchema.swift index ef6f2c7..2d61cd9 100644 --- a/Sources/CoreStoreSchema.swift +++ b/Sources/CoreStoreSchema.swift @@ -339,6 +339,23 @@ public final class CoreStoreSchema: DynamicSchema { keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter) fieldCoders[attribute.keyPath] = valueTransformer + + case let relationship as FieldRelationshipProtocol: + Internals.assert( + !NSManagedObject.instancesRespond(to: Selector(relationship.keyPath)), + "Relationship Property name \"\(String(reflecting: entity.type)).\(relationship.keyPath)\" is not allowed because it collides with \"\(String(reflecting: NSManagedObject.self)).\(relationship.keyPath)\"" + ) + let entityDescriptionValues = relationship.entityDescriptionValues() + let description = NSRelationshipDescription() + description.name = relationship.keyPath + description.minCount = entityDescriptionValues.minCount + description.maxCount = entityDescriptionValues.maxCount + description.isOrdered = entityDescriptionValues.isOrdered + description.deleteRule = entityDescriptionValues.deleteRule + description.versionHashModifier = entityDescriptionValues.versionHashModifier + description.renamingIdentifier = entityDescriptionValues.renamingIdentifier + propertyDescriptions.append(description) + keyPathsByAffectedKeyPaths[relationship.keyPath] = entityDescriptionValues.affectedByKeyPaths case let attribute as AttributeProtocol: Internals.assert( @@ -437,6 +454,26 @@ public final class CoreStoreSchema: DynamicSchema { for property in entityType.metaProperties(includeSuperclasses: false) { switch property { + + case let relationship as FieldRelationshipProtocol: + let (destinationType, destinationKeyPath) = relationship.entityDescriptionValues().inverse + let destinationEntity = findEntity(for: destinationType) + let description = relationshipsByName[relationship.keyPath]! + description.destinationEntity = entityDescriptionsByEntity[destinationEntity]! + + if let destinationKeyPath = destinationKeyPath { + + let inverseRelationshipDescription = findInverseRelationshipMatching( + destinationEntity: destinationEntity, + destinationKeyPath: destinationKeyPath + ) + description.inverseRelationship = inverseRelationshipDescription + + inverseRelationshipDescription.inverseRelationship = description + inverseRelationshipDescription.destinationEntity = entityDescription + + description.destinationEntity!.properties = description.destinationEntity!.properties + } case let relationship as RelationshipProtocol: let (destinationType, destinationKeyPath) = relationship.entityDescriptionValues().inverse diff --git a/Sources/DynamicObject.swift b/Sources/DynamicObject.swift index 101d580..32ed92b 100644 --- a/Sources/DynamicObject.swift +++ b/Sources/DynamicObject.swift @@ -173,6 +173,9 @@ extension CoreStoreObject { case let property as FieldAttributeProtocol: attributes[property.keyPath] = type(of: property).read(field: property, for: object.rawObject!) + case let property as FieldRelationshipProtocol: + attributes[property.keyPath] = type(of: property).valueForSnapshot(field: property, for: object.rawObject!) + case let property as AttributeProtocol: attributes[property.keyPath] = property.valueForSnapshot diff --git a/Sources/FIeldRelationshipType.swift b/Sources/FIeldRelationshipType.swift new file mode 100644 index 0000000..f054adc --- /dev/null +++ b/Sources/FIeldRelationshipType.swift @@ -0,0 +1,142 @@ +// +// FIeldRelationshipType.swift +// CoreStore +// +// Copyright © 2020 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 CoreData +import Foundation + + +// MARK: - FieldRelationshipType + +public protocol FieldRelationshipType { + + associatedtype DestinationObjectType: CoreStoreObject + + associatedtype NativeValueType: AnyObject + + associatedtype SnapshotValueType + + static func cs_toReturnType(from value: NativeValueType?) -> Self + + static func cs_toNativeType(from value: Self) -> NativeValueType? + + static func cs_valueForSnapshot(from value: NativeValueType?) -> SnapshotValueType +} + +public protocol FieldRelationshipToOneType: FieldRelationshipType {} + + +public protocol FieldRelationshipToManyType: FieldRelationshipType where Self: Sequence {} +public protocol FieldRelationshipToManyOrderedType: FieldRelationshipToManyType {} +public protocol FieldRelationshipToManyUnorderedType: FieldRelationshipToManyType {} + + +extension Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrapped: CoreStoreObject { + + public typealias DestinationObjectType = Wrapped + + public typealias NativeValueType = NSManagedObject + + public typealias SnapshotValueType = NSManagedObjectID? + + public static func cs_toReturnType(from value: NativeValueType?) -> Self { + + return value.map(Wrapped.cs_fromRaw(object:)) + } + + public static func cs_toNativeType(from value: Self) -> NativeValueType? { + + return value?.cs_toRaw() + } + + public static func cs_valueForSnapshot(from value: NativeValueType?) -> SnapshotValueType { + + return value?.objectID + } +} + + +extension Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelationshipToManyOrderedType where Element: CoreStoreObject { + + public typealias DestinationObjectType = Element + + public typealias NativeValueType = NSOrderedSet + + public typealias SnapshotValueType = [NSManagedObjectID] + + public static func cs_toReturnType(from value: NativeValueType?) -> Self { + + guard let value = value else { + + return [] + } + return value.map({ Element.cs_fromRaw(object: $0 as! NSManagedObject) }) + } + + public static func cs_toNativeType(from value: Self) -> NativeValueType? { + + return NSOrderedSet(array: value.map({ $0.rawObject! })) + } + + public static func cs_valueForSnapshot(from value: NativeValueType?) -> SnapshotValueType { + + guard let value = value else { + + return [] + } + return value.map({ ($0 as! NSManagedObject).objectID }) + } +} + +extension Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelationshipToManyUnorderedType where Element: CoreStoreObject { + + public typealias DestinationObjectType = Element + + public typealias NativeValueType = NSSet + + public typealias SnapshotValueType = Set + + public static func cs_toReturnType(from value: NativeValueType?) -> Self { + + guard let value = value else { + + return [] + } + return Set(value.map({ Element.cs_fromRaw(object: $0 as! NSManagedObject) })) + } + + public static func cs_toNativeType(from value: Self) -> NativeValueType? { + + return NSSet(array: value.map({ $0.rawObject! })) + } + + public static func cs_valueForSnapshot(from value: NativeValueType?) -> SnapshotValueType { + + guard let value = value else { + + return [] + } + return .init(value.map({ ($0 as! NSManagedObject).objectID })) + } +} diff --git a/Sources/Field.Relationship.swift b/Sources/Field.Relationship.swift new file mode 100644 index 0000000..005dff9 --- /dev/null +++ b/Sources/Field.Relationship.swift @@ -0,0 +1,346 @@ +// +// Field.ToOne.swift +// CoreStore +// +// Copyright © 2020 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 CoreData +import Foundation + + +// MARK: - FieldContainer + +extension FieldContainer { + + // MARK: - Relationship + + @propertyWrapper +// @dynamicMemberLookup + public struct Relationship: RelationshipKeyPathStringConvertible, FieldRelationshipProtocol { + + public typealias DeleteRule = RelationshipContainer.DeleteRule + + + // MARK: @propertyWrapper + + @available(*, unavailable) + public var wrappedValue: V { + + get { fatalError() } + set { fatalError() } + } + + public var projectedValue: Self { + + return self + } + + public static subscript( + _enclosingInstance instance: O, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> V { + + get { + + Internals.assert( + instance.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V + } + set { + + Internals.assert( + instance.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return self.modify(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, newValue: newValue) + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = V.DestinationObjectType + + + // MARK: RelationshipKeyPathStringConvertible + + public typealias ReturnValueType = V + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: FieldProtocol + + internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + + Internals.assert( + rawObject.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + let field = field as! Self + let keyPath = field.keyPath + return V.cs_toReturnType( + from: rawObject.value(forKey: keyPath) as! V.NativeValueType? + ) + } + + internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) { + + Internals.assert( + rawObject.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + rawObject.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + let newValue = newValue as! V + let field = field as! Self + let keyPath = field.keyPath + return rawObject.setValue( + V.cs_toNativeType(from: newValue), + forKey: keyPath + ) + } + + + // MARK: FieldRelationshipProtocol + + internal let entityDescriptionValues: () -> FieldRelationshipProtocol.EntityDescriptionValues + + internal static func valueForSnapshot(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + + Internals.assert( + rawObject.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + let field = field as! Self + let keyPath = field.keyPath + return V.cs_valueForSnapshot( + from: rawObject.value(forKey: keyPath) as! V.NativeValueType? + ) + } + + + // MARK: FilePrivate + + fileprivate init( + keyPath: KeyPathString, + isToMany: Bool, + isOrdered: Bool, + deleteRule: DeleteRule, + inverseKeyPath: @escaping () -> KeyPathString?, + versionHashModifier: @escaping () -> String?, + renamingIdentifier: @escaping () -> String?, + affectedByKeyPaths: @escaping () -> Set, + minCount: Int, + maxCount: Int + ) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + + let range = (Swift.max(0, minCount) ... maxCount) + return ( + isToMany: isToMany, + isOrdered: isOrdered, + deleteRule: deleteRule.nativeValue, + inverse: (type: V.DestinationObjectType.self, keyPath: inverseKeyPath()), + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + minCount: range.lowerBound, + maxCount: range.upperBound + ) + } + } + } +} + +extension FieldContainer.Relationship where V: FieldRelationshipToOneType { + + public init( + _ keyPath: KeyPathString, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + keyPath: keyPath, + isToMany: false, + isOrdered: false, + deleteRule: deleteRule, + inverseKeyPath: { nil }, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + affectedByKeyPaths: affectedByKeyPaths, + minCount: 0, + maxCount: 1 + ) + } + + public init( + _ keyPath: KeyPathString, + inverse: KeyPath.Relationship>, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where D: FieldRelationshipType { + + self.init( + keyPath: keyPath, + isToMany: false, + isOrdered: false, + deleteRule: deleteRule, + inverseKeyPath: { V.DestinationObjectType.meta[keyPath: inverse].keyPath }, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + affectedByKeyPaths: affectedByKeyPaths, + minCount: 0, + maxCount: 1 + ) + } +} + +extension FieldContainer.Relationship: ToManyRelationshipKeyPathStringConvertible where V: FieldRelationshipToManyType {} + +extension FieldContainer.Relationship where V: FieldRelationshipToManyOrderedType { + + public init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + keyPath: keyPath, + isToMany: true, + isOrdered: true, + deleteRule: deleteRule, + inverseKeyPath: { nil }, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + affectedByKeyPaths: affectedByKeyPaths, + minCount: minCount, + maxCount: maxCount + ) + } + + public init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (V.DestinationObjectType) -> FieldContainer.Relationship, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where D: FieldRelationshipType { + + self.init( + keyPath: keyPath, + isToMany: true, + isOrdered: true, + deleteRule: deleteRule, + inverseKeyPath: { inverse(V.DestinationObjectType.meta).keyPath }, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + affectedByKeyPaths: affectedByKeyPaths, + minCount: minCount, + maxCount: maxCount + ) + } +} + +extension FieldContainer.Relationship where V: FieldRelationshipToManyUnorderedType { + + public init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + keyPath: keyPath, + isToMany: true, + isOrdered: false, + deleteRule: deleteRule, + inverseKeyPath: { nil }, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + affectedByKeyPaths: affectedByKeyPaths, + minCount: minCount, + maxCount: maxCount + ) + } + + public init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (V.DestinationObjectType) -> FieldContainer.Relationship, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where D: FieldRelationshipType { + + self.init( + keyPath: keyPath, + isToMany: true, + isOrdered: false, + deleteRule: deleteRule, + inverseKeyPath: { inverse(V.DestinationObjectType.meta).keyPath }, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + affectedByKeyPaths: affectedByKeyPaths, + minCount: minCount, + maxCount: maxCount + ) + } +} diff --git a/Sources/FieldRelationshipProtocol.swift b/Sources/FieldRelationshipProtocol.swift new file mode 100644 index 0000000..319ea60 --- /dev/null +++ b/Sources/FieldRelationshipProtocol.swift @@ -0,0 +1,49 @@ +// +// FieldRelationshipProtocol.swift +// CoreStore +// +// Copyright © 2020 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: - FieldRelationshipProtocol + +internal protocol FieldRelationshipProtocol: FieldProtocol { + + typealias EntityDescriptionValues = ( + isToMany: Bool, + isOrdered: Bool, + deleteRule: NSDeleteRule, + inverse: (type: CoreStoreObject.Type, KeyPathString?), + versionHashModifier: String?, + renamingIdentifier: String?, + affectedByKeyPaths: Set, + minCount: Int, + maxCount: Int + ) + + var entityDescriptionValues: () -> EntityDescriptionValues { get } + + static func valueForSnapshot(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? +} diff --git a/Sources/KeyPath+Querying.swift b/Sources/KeyPath+Querying.swift index 333ae49..4cdbb88 100644 --- a/Sources/KeyPath+Querying.swift +++ b/Sources/KeyPath+Querying.swift @@ -447,6 +447,17 @@ public func ~= (_ sequence: S, _ keyPath: KeyPath.Required +/** + Creates a `Where` clause by comparing if a property is less than a value + ``` + let person = dataStack.fetchOne(From().where(\.$age < 20)) + ``` + */ +public func < (_ keyPath: KeyPath.Stored>, _ value: V) -> Where { + + return Where("%K < %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is less than a value ``` @@ -458,6 +469,17 @@ public func < (_ keyPath: KeyPath.Require return Where("%K < %@", O.meta[keyPath: keyPath].keyPath, value.cs_toQueryableNativeType()) } +/** + Creates a `Where` clause by comparing if a property is greater than a value + ``` + let person = dataStack.fetchOne(From().where(\.$age > 20)) + ``` + */ +public func > (_ keyPath: KeyPath.Stored>, _ value: V) -> Where { + + return Where("%K > %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is greater than a value ``` @@ -469,6 +491,17 @@ public func > (_ keyPath: KeyPath.Require return Where("%K > %@", O.meta[keyPath: keyPath].keyPath, value.cs_toQueryableNativeType()) } +/** + Creates a `Where` clause by comparing if a property is less than or equal to a value + ``` + let person = dataStack.fetchOne(From().where(\.$age <= 20)) + ``` + */ +public func <= (_ keyPath: KeyPath.Stored>, _ value: V) -> Where { + + return Where("%K <= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is less than or equal to a value ``` @@ -480,6 +513,17 @@ public func <= (_ keyPath: KeyPath.Requir return Where("%K <= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toQueryableNativeType()) } +/** + Creates a `Where` clause by comparing if a property is greater than or equal to a value + ``` + let person = dataStack.fetchOne(From().where(\.$age >= 20)) + ``` + */ +public func >= (_ keyPath: KeyPath.Stored>, _ value: V) -> Where { + + return Where("%K >= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is greater than or equal to a value ``` @@ -494,6 +538,17 @@ public func >= (_ keyPath: KeyPath.Requir // MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer.Optional +/** + Creates a `Where` clause by comparing if a property is less than a value + ``` + let person = dataStack.fetchOne(From().where(\.$age < 20)) + ``` + */ +public func < (_ keyPath: KeyPath.Stored>, _ value: V) -> Where where V.Wrapped: Comparable { + + return Where("%K < %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is less than a value ``` @@ -512,6 +567,17 @@ public func < (_ keyPath: KeyPath.Optional>, _ val } } +/** + Creates a `Where` clause by comparing if a property is greater than a value + ``` + let person = dataStack.fetchOne(From().where(\.$age > 20)) + ``` + */ +public func > (_ keyPath: KeyPath.Stored>, _ value: V) -> Where where V.Wrapped: Comparable { + + return Where("%K > %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is greater than a value ``` @@ -530,6 +596,17 @@ public func > (_ keyPath: KeyPath.Optional>, _ val } } +/** + Creates a `Where` clause by comparing if a property is less than or equal to a value + ``` + let person = dataStack.fetchOne(From().where(\.$age <= 20)) + ``` + */ +public func <= (_ keyPath: KeyPath.Stored>, _ value: V) -> Where where V.Wrapped: Comparable { + + return Where("%K <= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is less than or equal to a value ``` @@ -548,6 +625,17 @@ public func <= (_ keyPath: KeyPath.Optional>, _ va } } +/** + Creates a `Where` clause by comparing if a property is greater than or equal to a value + ``` + let person = dataStack.fetchOne(From().where(\.$age >= 20)) + ``` + */ +public func >= (_ keyPath: KeyPath.Stored>, _ value: V) -> Where where V.Wrapped: Comparable { + + return Where("%K >= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType) +} + /** Creates a `Where` clause by comparing if a property is greater than or equal to a value ``` @@ -569,6 +657,17 @@ public func >= (_ keyPath: KeyPath.Optional>, _ va // MARK: - KeyPath where Root: CoreStoreObject, Value: RelationshipContainer.ToOne +/** + Creates a `Where` clause by comparing if a property is equal to a value + ``` + let dog = dataStack.fetchOne(From().where(\.$master == john)) + ``` + */ +public func == (_ keyPath: KeyPath.Relationship>, _ object: D.DestinationObjectType?) -> Where { + + return Where(O.meta[keyPath: keyPath].keyPath, isEqualTo: object) +} + /** Creates a `Where` clause by comparing if a property is equal to a value ``` @@ -591,6 +690,17 @@ public func == (_ keyPath: KeyPath.ToOne>, return Where(O.meta[keyPath: keyPath].keyPath, isEqualTo: object) } +/** + Creates a `Where` clause by comparing if a property is not equal to a value + ``` + let dog = dataStack.fetchOne(From().where(\.$master != john)) + ``` + */ +public func != (_ keyPath: KeyPath.Relationship>, _ object: D.DestinationObjectType?) -> Where { + + return !Where(O.meta[keyPath: keyPath].keyPath, isEqualTo: object) +} + /** Creates a `Where` clause by comparing if a property is not equal to a value ``` @@ -613,6 +723,17 @@ public func != (_ keyPath: KeyPath.ToOne>, return !Where(O.meta[keyPath: keyPath].keyPath, isEqualTo: object) } +/** + Creates a `Where` clause by checking if a sequence contains a value of a property + ``` + let dog = dataStack.fetchOne(From().where([john, bob, joe] ~= \.$master)) + ``` + */ +public func ~= (_ sequence: S, _ keyPath: KeyPath.Relationship>) -> Where where S.Iterator.Element == D.DestinationObjectType { + + return Where(O.meta[keyPath: keyPath].keyPath, isMemberOf: sequence) +} + /** Creates a `Where` clause by checking if a sequence contains a value of a property ``` diff --git a/Sources/Select.swift b/Sources/Select.swift index dab14fb..91c43c4 100644 --- a/Sources/Select.swift +++ b/Sources/Select.swift @@ -379,45 +379,15 @@ extension SelectTerm where O: NSManagedObject { // MARK: - SelectTerm where O: CoreStoreObject extension SelectTerm where O: CoreStoreObject { - + /** Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. - parameter keyPath: the attribute name - returns: a `SelectTerm` to a `Select` clause for querying an entity attribute */ - public static func attribute(_ keyPath: KeyPath.Required>) -> SelectTerm { - - return self.attribute(O.meta[keyPath: keyPath].keyPath) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. - - parameter keyPath: the attribute name - - returns: a `SelectTerm` to a `Select` clause for querying an entity attribute - */ - public static func attribute(_ keyPath: KeyPath.Optional>) -> SelectTerm { - - return self.attribute(O.meta[keyPath: keyPath].keyPath) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. - - parameter keyPath: the attribute name - - returns: a `SelectTerm` to a `Select` clause for querying an entity attribute - */ - public static func attribute(_ keyPath: KeyPath.Required>) -> SelectTerm { - - return self.attribute(O.meta[keyPath: keyPath].keyPath) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. - - parameter keyPath: the attribute name - - returns: a `SelectTerm` to a `Select` clause for querying an entity attribute - */ - public static func attribute(_ keyPath: KeyPath.Optional>) -> SelectTerm { - - return self.attribute(O.meta[keyPath: keyPath].keyPath) + public static func attribute(_ keyPath: KeyPath) -> SelectTerm where K.ObjectType == O { + + return self.attribute(O.meta[keyPath: keyPath].cs_keyPathString) } /** @@ -426,42 +396,9 @@ extension SelectTerm where O: CoreStoreObject { - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average()" is used - returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute */ - public static func average(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { + public static func average(_ keyPath: KeyPath, as alias: KeyPathString? = nil) -> SelectTerm where K.ObjectType == O{ - return self.average(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute - */ - public static func average(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.average(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute - */ - public static func average(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.average(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute - */ - public static func average(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.average(O.meta[keyPath: keyPath].keyPath, as: alias) + return self.average(O.meta[keyPath: keyPath].cs_keyPathString, as: alias) } /** @@ -470,46 +407,10 @@ extension SelectTerm where O: CoreStoreObject { - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count()" is used - returns: a `SelectTerm` to a `Select` clause for a count query */ - public static func count(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { + public static func count(_ keyPath: KeyPath, as alias: KeyPathString? = nil) -> SelectTerm where K.ObjectType == O { - return self.count(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for a count query. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count()" is used - - returns: a `SelectTerm` to a `Select` clause for a count query - */ - public static func count(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.count(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for a count query. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count()" is used - - returns: a `SelectTerm` to a `Select` clause for a count query - */ - public static func count(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.count(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for a count query. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count()" is used - - returns: a `SelectTerm` to a `Select` clause for a count query - */ - public static func count(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.count(O.meta[keyPath: keyPath].keyPath, as: alias) + return self.count(O.meta[keyPath: keyPath].cs_keyPathString, as: alias) } /** @@ -518,46 +419,10 @@ extension SelectTerm where O: CoreStoreObject { - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max()" is used - returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute */ - public static func maximum(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { + public static func maximum(_ keyPath: KeyPath, as alias: KeyPathString? = nil) -> SelectTerm where K.ObjectType == O { - return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute - */ - public static func maximum(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute - */ - public static func maximum(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute - */ - public static func maximum(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias) + return self.maximum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias) } /** @@ -566,42 +431,9 @@ extension SelectTerm where O: CoreStoreObject { - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min()" is used - returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute */ - public static func minimum(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { + public static func minimum(_ keyPath: KeyPath, as alias: KeyPathString? = nil) -> SelectTerm where K.ObjectType == O { - return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute - */ - public static func minimum(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute - */ - public static func minimum(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute - */ - public static func minimum(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias) + return self.minimum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias) } /** @@ -610,42 +442,9 @@ extension SelectTerm where O: CoreStoreObject { - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum()" is used - returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute */ - public static func sum(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { + public static func sum(_ keyPath: KeyPath, as alias: KeyPathString? = nil) -> SelectTerm where K.ObjectType == O { - return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute - */ - public static func sum(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute - */ - public static func sum(_ keyPath: KeyPath.Required>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias) - } - - /** - Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute. - - parameter keyPath: the attribute name - - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum()" is used - - returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute - */ - public static func sum(_ keyPath: KeyPath.Optional>, as alias: KeyPathString? = nil) -> SelectTerm { - - return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias) + return self.sum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias) } } diff --git a/Sources/Where.Expression.swift b/Sources/Where.Expression.swift index 8cb7cb8..8c0a0e7 100644 --- a/Sources/Where.Expression.swift +++ b/Sources/Where.Expression.swift @@ -235,6 +235,20 @@ public func ~ ().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 ``` @@ -277,6 +291,20 @@ public func ~ ().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 ``` diff --git a/Sources/Where.swift b/Sources/Where.swift index f6b3af7..f9de0c8 100644 --- a/Sources/Where.swift +++ b/Sources/Where.swift @@ -451,6 +451,17 @@ extension Where where O: CoreStoreObject { self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value) } + + /** + Initializes a `Where` clause that compares equality + + - parameter keyPath: the keyPath to compare with + - parameter value: the arguments for the `==` operator + */ + public init(_ keyPath: KeyPath.Relationship>, isEqualTo value: V.DestinationObjectType?) { + + self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value) + } /** Initializes a `Where` clause that compares equality to `nil`