Field.Relationship propertyWrapper

This commit is contained in:
John Estropia
2020-01-20 17:13:01 +09:00
parent bcc2d9def3
commit 92ad895044
12 changed files with 881 additions and 304 deletions

View File

@@ -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 = "<group>"; };
B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = "<group>"; };
B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = "<group>"; };
B57E6FA123D302FA000FD031 /* Field.Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Relationship.swift; sourceTree = "<group>"; };
B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FIeldRelationshipType.swift; sourceTree = "<group>"; };
B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldRelationshipProtocol.swift; sourceTree = "<group>"; };
B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = "<group>"; };
B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisherTests.swift; sourceTree = "<group>"; };
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeProtocol.swift; sourceTree = "<group>"; };
@@ -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 = "<group>";
@@ -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 */,

View File

@@ -44,7 +44,8 @@ class Animal: CoreStoreObject {
@Field.Coded("color", coder: FieldCoders.NSCoding.self)
var color: Color? = .blue
let master = Relationship.ToOne<Person>("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<Int>("age", initial: 1)
let friends = Relationship.ToManyOrdered<Dog>("friends")
let friendedBy = Relationship.ToManyUnordered<Dog>("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<Dog>
}
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<Person>("spouse")
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master })
@Field.Relationship("spouse")
var spouse: Person?
private let _spouse = Relationship.ToOne<Person>("_spouseInverse", inverse: { $0.spouse })
@Field.Relationship("pets", inverse: \.$master)
var pets: Set<Animal>
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<Person>, _ newValue: String) {
partialObject.setPrimitiveValue(newValue, for: \.$title)
partialObject.setPrimitiveValue(nil, for: { $0.$displayName })
partialObject.setPrimitiveValue(nil, for: \.$displayName)
}
private static func setName(_ partialObject: PartialObject<Person>, _ newValue: String) {
@@ -226,8 +235,8 @@ class DynamicModelTests: BaseTestDataTestCase {
case String(keyPath: \Animal.$species):
XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
case String(keyPath: \Animal.master):
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
case String(keyPath: \Animal.$master):
XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
case String(keyPath: \Animal.$color):
XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
@@ -240,7 +249,7 @@ class DynamicModelTests: BaseTestDataTestCase {
let dog = transaction.create(Into<Dog>())
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<Animal>.Stored<String>)
case String(keyPath: \Dog.master):
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
case String(keyPath: \Dog.$master):
XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
case String(keyPath: \Dog.$color):
XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
@@ -258,14 +267,14 @@ class DynamicModelTests: BaseTestDataTestCase {
case String(keyPath: \Dog.$nickname):
XCTAssertTrue(property is FieldContainer<Dog>.Stored<String?>)
case String(keyPath: \Dog.age):
XCTAssertTrue(property is ValueContainer<Dog>.Required<Int>)
case String(keyPath: \Dog.$age):
XCTAssertTrue(property is FieldContainer<Dog>.Stored<Int>)
case String(keyPath: \Dog.friends):
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyOrdered<Dog>)
case String(keyPath: \Dog.$friends):
XCTAssertTrue(property is FieldContainer<Dog>.Relationship<[Dog]>)
case String(keyPath: \Dog.friendedBy):
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyUnordered<Dog>)
case String(keyPath: \Dog.$friendedBy):
XCTAssertTrue(property is FieldContainer<Dog>.Relationship<Set<Dog>>)
default:
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
@@ -325,7 +334,7 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertEqual(dog.nickname, "Spot")
let person = transaction.create(Into<Person>())
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<Dog>({ $0.age == 10 })
let p3 = Where<Dog>({ $0.$age == 10 })
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
let totalAge = try transaction.queryValue(From<Dog>().select(Int.self, .sum(\Dog.age)))
let totalAge = try transaction.queryValue(
From<Dog>().select(Int.self, .sum(\.$age))
)
XCTAssertEqual(totalAge, 1)
_ = try transaction.fetchAll(
From<Dog>()
.where(\Animal.$species == "Dog" && \Dog.age == 10)
.where(\Animal.$species == "Dog" && \Dog.$age == 10)
)
_ = try transaction.fetchAll(
From<Dog>()
.where(\Dog.age == 10 && \Animal.$species == "Dog")
.where(\Dog.$age == 10 && \Animal.$species == "Dog")
.orderBy(.ascending({ $0.$species }))
)
_ = try transaction.fetchAll(
From<Dog>(),
Where<Dog>({ $0.age > 10 && $0.age <= 15 })
Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
)
_ = try transaction.fetchAll(
From<Dog>(),
Where<Dog>({ $0.$species == "Dog" && $0.age == 10 })
Where<Dog>({ $0.$species == "Dog" && $0.$age == 10 })
)
_ = try transaction.fetchAll(
From<Dog>(),
Where<Dog>({ $0.age == 10 && $0.$species == "Dog" })
Where<Dog>({ $0.$age == 10 && $0.$species == "Dog" })
)
_ = try transaction.fetchAll(
From<Dog>(),
Where<Dog>({ $0.age > 10 && $0.age <= 15 })
Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
)
_ = try transaction.fetchAll(
From<Dog>(),
(\Dog.age > 10 && \Dog.age <= 15)
(\Dog.$age > 10 && \Dog.$age <= 15)
)
},
success: { _ in

View File

@@ -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<Animal> = (\.master ~ \.$name) == dummy
let whereClause: Where<Animal> = (\.$master ~ \.$name) == dummy
let predicate = NSPredicate(format: "master.name == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -265,7 +265,7 @@ final class WhereTests: XCTestCase {
}
do {
let whereClause: Where<Animal> = (\.master ~ \.spouse ~ \.$name) == dummy
let whereClause: Where<Animal> = (\.$master ~ \.$spouse ~ \.$name) == dummy
let predicate = NSPredicate(format: "master.spouse.name == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -283,7 +283,7 @@ final class WhereTests: XCTestCase {
}
do {
let whereClause: Where<Animal> = (\.master ~ \.pets).count() == count
let whereClause: Where<Animal> = (\.$master ~ \.$pets).count() == count
let predicate = NSPredicate(format: "master.pets.@count == %d", count)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -301,7 +301,7 @@ final class WhereTests: XCTestCase {
}
do {
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.$species).any() == dummy
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).any() == dummy
let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -319,7 +319,7 @@ final class WhereTests: XCTestCase {
}
do {
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.$species).all() == dummy
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).all() == dummy
let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -337,7 +337,7 @@ final class WhereTests: XCTestCase {
}
do {
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.$species).none() == dummy
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).none() == dummy
let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate)

View File

@@ -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

View File

@@ -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

View File

@@ -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<NSManagedObjectID>
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 }))
}
}

View File

@@ -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<V: FieldRelationshipType>: RelationshipKeyPathStringConvertible, FieldRelationshipProtocol {
public typealias DeleteRule = RelationshipContainer<O>.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<O, V>,
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
) -> 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<KeyPathString>,
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<KeyPathString> = []
) {
self.init(
keyPath: keyPath,
isToMany: false,
isOrdered: false,
deleteRule: deleteRule,
inverseKeyPath: { nil },
versionHashModifier: versionHashModifier,
renamingIdentifier: previousVersionKeyPath,
affectedByKeyPaths: affectedByKeyPaths,
minCount: 0,
maxCount: 1
)
}
public init<D>(
_ keyPath: KeyPathString,
inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) 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<KeyPathString> = []
) {
self.init(
keyPath: keyPath,
isToMany: true,
isOrdered: true,
deleteRule: deleteRule,
inverseKeyPath: { nil },
versionHashModifier: versionHashModifier,
renamingIdentifier: previousVersionKeyPath,
affectedByKeyPaths: affectedByKeyPaths,
minCount: minCount,
maxCount: maxCount
)
}
public init<D>(
_ keyPath: KeyPathString,
minCount: Int = 0,
maxCount: Int = 0,
inverse: @escaping (V.DestinationObjectType) -> FieldContainer<V.DestinationObjectType>.Relationship<D>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) 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<KeyPathString> = []
) {
self.init(
keyPath: keyPath,
isToMany: true,
isOrdered: false,
deleteRule: deleteRule,
inverseKeyPath: { nil },
versionHashModifier: versionHashModifier,
renamingIdentifier: previousVersionKeyPath,
affectedByKeyPaths: affectedByKeyPaths,
minCount: minCount,
maxCount: maxCount
)
}
public init<D>(
_ keyPath: KeyPathString,
minCount: Int = 0,
maxCount: Int = 0,
inverse: @escaping (V.DestinationObjectType) -> FieldContainer<V.DestinationObjectType>.Relationship<D>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
) 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
)
}
}

View File

@@ -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<KeyPathString>,
minCount: Int,
maxCount: Int
)
var entityDescriptionValues: () -> EntityDescriptionValues { get }
static func valueForSnapshot(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any?
}

View File

@@ -447,6 +447,17 @@ public func ~= <O, V, S: Sequence>(_ sequence: S, _ keyPath: KeyPath<O, ValueCon
// MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer<Root>.Required<QueryableAttributeType & Comparable>
/**
Creates a `Where` clause by comparing if a property is less than a value
```
let person = dataStack.fetchOne(From<Person>().where(\.$age < 20))
```
*/
public func < <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
return Where<O>("%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 < <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Require
return Where<O>("%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<Person>().where(\.$age > 20))
```
*/
public func > <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
return Where<O>("%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 > <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Require
return Where<O>("%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<Person>().where(\.$age <= 20))
```
*/
public func <= <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
return Where<O>("%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 <= <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Requir
return Where<O>("%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<Person>().where(\.$age >= 20))
```
*/
public func >= <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
return Where<O>("%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 >= <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Requir
// MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer<Root>.Optional<QueryableAttributeType & Comparable>
/**
Creates a `Where` clause by comparing if a property is less than a value
```
let person = dataStack.fetchOne(From<Person>().where(\.$age < 20))
```
*/
public func < <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
return Where<O>("%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 < <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ val
}
}
/**
Creates a `Where` clause by comparing if a property is greater than a value
```
let person = dataStack.fetchOne(From<Person>().where(\.$age > 20))
```
*/
public func > <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
return Where<O>("%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 > <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ val
}
}
/**
Creates a `Where` clause by comparing if a property is less than or equal to a value
```
let person = dataStack.fetchOne(From<Person>().where(\.$age <= 20))
```
*/
public func <= <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
return Where<O>("%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 <= <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ va
}
}
/**
Creates a `Where` clause by comparing if a property is greater than or equal to a value
```
let person = dataStack.fetchOne(From<Person>().where(\.$age >= 20))
```
*/
public func >= <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
return Where<O>("%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 >= <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ va
// MARK: - KeyPath where Root: CoreStoreObject, Value: RelationshipContainer<Root>.ToOne<CoreStoreObject>
/**
Creates a `Where` clause by comparing if a property is equal to a value
```
let dog = dataStack.fetchOne(From<Dog>().where(\.$master == john))
```
*/
public func == <O, D: FieldRelationshipToOneType>(_ keyPath: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ object: D.DestinationObjectType?) -> Where<O> {
return Where<O>(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 == <O, D>(_ keyPath: KeyPath<O, RelationshipContainer<O>.ToOne<D>>,
return Where<O>(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<Dog>().where(\.$master != john))
```
*/
public func != <O, D: FieldRelationshipToOneType>(_ keyPath: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ object: D.DestinationObjectType?) -> Where<O> {
return !Where<O>(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 != <O, D>(_ keyPath: KeyPath<O, RelationshipContainer<O>.ToOne<D>>,
return !Where<O>(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<Dog>().where([john, bob, joe] ~= \.$master))
```
*/
public func ~= <O, D: FieldRelationshipToOneType, S: Sequence>(_ sequence: S, _ keyPath: KeyPath<O, FieldContainer<O>.Relationship<D>>) -> Where<O> where S.Iterator.Element == D.DestinationObjectType {
return Where<O>(O.meta[keyPath: keyPath].keyPath, isMemberOf: sequence)
}
/**
Creates a `Where` clause by checking if a sequence contains a value of a property
```

View File

@@ -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<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>) -> SelectTerm<O> {
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<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>) -> SelectTerm<O> {
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<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>) -> SelectTerm<O> {
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<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>) -> SelectTerm<O> {
return self.attribute(O.meta[keyPath: keyPath].keyPath)
public static func attribute<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>) -> SelectTerm<O> 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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
*/
public static func average<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
public static func average<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>, as alias: KeyPathString? = nil) -> SelectTerm<O> 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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
*/
public static func average<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
*/
public static func average<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
*/
public static func average<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
*/
public static func count<V>(_ keyPath: KeyPath<O,
ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
public static func count<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O,
K>, as alias: KeyPathString? = nil) -> SelectTerm<O> 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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
*/
public static func count<V>(_ keyPath: KeyPath<O,
ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
*/
public static func count<V>(_ keyPath: KeyPath<O,
TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
*/
public static func count<V>(_ keyPath: KeyPath<O,
TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
*/
public static func maximum<V>(_ keyPath: KeyPath<O,
ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
public static func maximum<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O,
K>, as alias: KeyPathString? = nil) -> SelectTerm<O> 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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
*/
public static func maximum<V>(_ keyPath: KeyPath<O,
ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
*/
public static func maximum<V>(_ keyPath: KeyPath<O,
TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
*/
public static func maximum<V>(_ keyPath: KeyPath<O,
TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
*/
public static func minimum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
public static func minimum<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>, as alias: KeyPathString? = nil) -> SelectTerm<O> 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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
*/
public static func minimum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
*/
public static func minimum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
*/
public static func minimum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func sum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
public static func sum<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>, as alias: KeyPathString? = nil) -> SelectTerm<O> 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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func sum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func sum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<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(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func sum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias)
return self.sum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias)
}
}

View File

@@ -235,6 +235,20 @@ public func ~ <O: NSManagedObject, D: NSManagedObject, T, C: AllowedObjectiveCTo
// MARK: - ~ where D: CoreStoreObject
/**
Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions
```
let owner = dataStack.fetchOne(From<Pet>().where((\.$master ~ \.$name) == "John"))
```
*/
public func ~ <O: CoreStoreObject, D: FieldRelationshipToOneType, K: KeyPathStringConvertible>(_ lhs: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ rhs: KeyPath<D.DestinationObjectType, K>) -> Where<O>.Expression<Where<O>.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 ~ <O: CoreStoreObject, D: CoreStoreObject, T, K: KeyPathStringConver
)
}
/**
Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions
```
let happyPets = dataStack.fetchAll(From<Pet>().where((\.$master ~ \.$pets).count() > 1))
```
*/
public func ~ <O: CoreStoreObject, D: FieldRelationshipToOneType, K: ToManyRelationshipKeyPathStringConvertible>(_ lhs: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ rhs: KeyPath<D.DestinationObjectType, K>) -> Where<O>.Expression<Where<O>.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
```

View File

@@ -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<V: FieldRelationshipToOneType>(_ keyPath: KeyPath<O, FieldContainer<O>.Relationship<V>>, isEqualTo value: V.DestinationObjectType?) {
self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value)
}
/**
Initializes a `Where` clause that compares equality to `nil`