Skip to content

Commit e8ecfb6

Browse files
committed
Add Expression.relationship()
1 parent e3255d8 commit e8ecfb6

5 files changed

Lines changed: 80 additions & 34 deletions

File tree

Sources/CoreDataModel/NSPredicate.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ public extension Expression {
119119

120120
switch self {
121121
case let .keyPath(keyPath): return NSExpression(forKeyPath: keyPath.rawValue)
122-
case let .value(value): return NSExpression(forConstantValue: value.toFoundation())
122+
case let .attribute(value): return NSExpression(forConstantValue: value.toFoundation())
123+
case let .relationship(value): return NSExpression(forConstantValue: value.toFoundation())
123124
}
124125
}
125126
}
@@ -146,4 +147,23 @@ internal extension AttributeValue {
146147
}
147148
}
148149

150+
internal extension RelationshipValue {
151+
152+
func toFoundation() -> AnyObject? {
153+
154+
switch self {
155+
case .null: return nil
156+
case let .toOne(value): return value.toFoundation()
157+
case let .toMany(value): return value.map({ $0.toFoundation() }) as NSArray
158+
}
159+
}
160+
}
161+
162+
internal extension ObjectID {
163+
164+
func toFoundation() -> AnyObject {
165+
rawValue as NSString
166+
}
167+
}
168+
149169
#endif

Sources/CoreModel/Predicate/Comparison.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public func != (lhs: Expression, rhs: Expression) -> FetchRequest.Predicate {
194194
public func < <T: AttributeEncodable>(lhs: String, rhs: T) -> FetchRequest.Predicate {
195195

196196
let comparison = FetchRequest.Predicate.Comparison(left: .keyPath(.init(rawValue: lhs)),
197-
right: .value(rhs.attributeValue),
197+
right: .attribute(rhs.attributeValue),
198198
type: .lessThan)
199199

200200
return .comparison(comparison)
@@ -203,7 +203,7 @@ public func < <T: AttributeEncodable>(lhs: String, rhs: T) -> FetchRequest.Predi
203203
public func <= <T: AttributeEncodable>(lhs: String, rhs: T) -> FetchRequest.Predicate {
204204

205205
let comparison = FetchRequest.Predicate.Comparison(left: .keyPath(.init(rawValue: lhs)),
206-
right: .value(rhs.attributeValue),
206+
right: .attribute(rhs.attributeValue),
207207
type: .lessThanOrEqualTo)
208208

209209
return .comparison(comparison)
@@ -212,7 +212,7 @@ public func <= <T: AttributeEncodable>(lhs: String, rhs: T) -> FetchRequest.Pred
212212
public func > <T: AttributeEncodable>(lhs: String, rhs: T) -> FetchRequest.Predicate {
213213

214214
let comparison = FetchRequest.Predicate.Comparison(left: .keyPath(.init(rawValue: lhs)),
215-
right: .value(rhs.attributeValue),
215+
right: .attribute(rhs.attributeValue),
216216
type: .greaterThan)
217217

218218
return .comparison(comparison)
@@ -221,7 +221,7 @@ public func > <T: AttributeEncodable>(lhs: String, rhs: T) -> FetchRequest.Predi
221221
public func >= <T: AttributeEncodable> (lhs: String, rhs: T) -> FetchRequest.Predicate {
222222

223223
let comparison = FetchRequest.Predicate.Comparison(left: .keyPath(.init(rawValue: lhs)),
224-
right: .value(rhs.attributeValue),
224+
right: .attribute(rhs.attributeValue),
225225
type: .greaterThanOrEqualTo)
226226

227227
return .comparison(comparison)
@@ -230,7 +230,7 @@ public func >= <T: AttributeEncodable> (lhs: String, rhs: T) -> FetchRequest.Pre
230230
public func == <T: AttributeEncodable> (lhs: String, rhs: T) -> FetchRequest.Predicate {
231231

232232
let comparison = FetchRequest.Predicate.Comparison(left: .keyPath(.init(rawValue: lhs)),
233-
right: .value(rhs.attributeValue),
233+
right: .attribute(rhs.attributeValue),
234234
type: .equalTo)
235235

236236
return .comparison(comparison)
@@ -239,7 +239,7 @@ public func == <T: AttributeEncodable> (lhs: String, rhs: T) -> FetchRequest.Pre
239239
public func != <T: AttributeEncodable> (lhs: String, rhs: T) -> FetchRequest.Predicate {
240240

241241
let comparison = FetchRequest.Predicate.Comparison(left: .keyPath(.init(rawValue: lhs)),
242-
right: .value(rhs.attributeValue),
242+
right: .attribute(rhs.attributeValue),
243243
type: .notEqualTo)
244244

245245
return .comparison(comparison)

Sources/CoreModel/Predicate/Expression.swift

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
/// Used to represent expressions in a predicate.
1010
public enum Expression: Equatable, Hashable, Sendable {
1111

12-
/// Expression that represents a given constant value.
13-
case value(AttributeValue)
12+
/// Expression that represents a given constant attribute value.
13+
case attribute(AttributeValue)
14+
15+
/// Expression that represents a given constant attribute value.
16+
case relationship(RelationshipValue)
1417

1518
/// Expression that invokes `value​For​Key​Path:​` with a given key path.
1619
case keyPath(PredicateKeyPath)
@@ -19,15 +22,17 @@ public enum Expression: Equatable, Hashable, Sendable {
1922
/// Type of predicate expression.
2023
public enum ExpressionType: String, Codable, Sendable {
2124

22-
case value
25+
case attribute
26+
case relationship
2327
case keyPath
2428
}
2529

2630
public extension Expression {
2731

2832
var type: ExpressionType {
2933
switch self {
30-
case .value: return .value
34+
case .attribute: return .attribute
35+
case .relationship: return .relationship
3136
case .keyPath: return .keyPath
3237
}
3338
}
@@ -40,8 +45,9 @@ extension Expression: CustomStringConvertible {
4045
public var description: String {
4146

4247
switch self {
43-
case let .value(value): return value.predicateDescription
44-
case let .keyPath(value): return value.description
48+
case let .attribute(value): return value.predicateDescription
49+
case let .relationship(value): return value.predicateDescription
50+
case let .keyPath(value): return value.description
4551
}
4652
}
4753
}
@@ -68,6 +74,21 @@ internal extension AttributeValue {
6874
}
6975
}
7076

77+
internal extension RelationshipValue {
78+
79+
var predicateDescription: String {
80+
81+
switch self {
82+
case .null:
83+
return "nil"
84+
case let .toOne(objectID):
85+
return objectID.rawValue
86+
case let .toMany(objectIDs):
87+
return "{" + objectIDs.reduce("", { $0 + ($0.isEmpty ? "" : ", ") + $1.rawValue }) + "}"
88+
}
89+
}
90+
}
91+
7192
// MARK: - Codable
7293

7394
extension Expression: Codable {
@@ -84,9 +105,12 @@ extension Expression: Codable {
84105
let type = try container.decode(ExpressionType.self, forKey: .type)
85106

86107
switch type {
87-
case .value:
108+
case .attribute:
88109
let expression = try container.decode(AttributeValue.self, forKey: .expression)
89-
self = .value(expression)
110+
self = .attribute(expression)
111+
case .relationship:
112+
let expression = try container.decode(RelationshipValue.self, forKey: .expression)
113+
self = .relationship(expression)
90114
case .keyPath:
91115
let keyPath = try container.decode(String.self, forKey: .expression)
92116
self = .keyPath(PredicateKeyPath(rawValue: keyPath))
@@ -99,7 +123,9 @@ extension Expression: Codable {
99123
try container.encode(type, forKey: .type)
100124

101125
switch self {
102-
case let .value(value):
126+
case let .attribute(value):
127+
try container.encode(value, forKey: .expression)
128+
case let .relationship(value):
103129
try container.encode(value, forKey: .expression)
104130
case let .keyPath(keyPath):
105131
try container.encode(keyPath.rawValue, forKey: .expression)

Tests/CoreModelTests/NSPredicateTests.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,22 @@ final class NSPredicateTests: XCTestCase {
1616

1717
func testDescription() {
1818

19-
XCTAssertEqual((.keyPath("name") == .value(.string("Coleman"))).description,
19+
XCTAssertEqual((.keyPath("name") == .attribute(.string("Coleman"))).description,
2020
NSPredicate(format: "name == \"Coleman\"").description)
21-
XCTAssertEqual(((.keyPath("name") != .value(.null)) as FetchRequest.Predicate).description,
21+
XCTAssertEqual(((.keyPath("name") != .attribute(.null)) as FetchRequest.Predicate).description,
2222
NSPredicate(format: "name != nil").description)
23-
XCTAssertEqual((!(.keyPath("name") == .value(.null))).description,
23+
XCTAssertEqual((!(.keyPath("name") == .attribute(.null))).description,
2424
NSPredicate(format: "NOT name == nil").description)
2525
}
2626

2727
func testComparison() {
2828

2929
let predicate: FetchRequest.Predicate = #keyPath(PersonObject.id) > Int64(0)
30-
&& (#keyPath(PersonObject.name)).compare(.notEqualTo, .value(.null))
30+
&& (#keyPath(PersonObject.name)).compare(.notEqualTo, .attribute(.null))
3131
&& (#keyPath(PersonObject.id)) != Int64(99)
3232
&& (#keyPath(PersonObject.id)) == Int64(1)
33-
&& (#keyPath(PersonObject.name)).compare(.beginsWith, .value(.string("C")))
34-
&& (#keyPath(PersonObject.name)).compare(.contains, [.diacriticInsensitive, .caseInsensitive], .value(.string("COLE")))
33+
&& (#keyPath(PersonObject.name)).compare(.beginsWith, .attribute(.string("C")))
34+
&& (#keyPath(PersonObject.name)).compare(.contains, [.diacriticInsensitive, .caseInsensitive], .attribute(.string("COLE")))
3535

3636
let converted = predicate.toFoundation()
3737

@@ -69,7 +69,7 @@ final class NSPredicateTests: XCTestCase {
6969

7070
let now = Date()
7171

72-
let predicate: FetchRequest.Predicate = (#keyPath(EventObject.name)).compare(.matches, [.caseInsensitive], .value(.string(#"\w+ event"#)))
72+
let predicate: FetchRequest.Predicate = (#keyPath(EventObject.name)).compare(.matches, [.caseInsensitive], .attribute(.string(#"\w+ event"#)))
7373
&& (#keyPath(EventObject.start)) < now
7474
&& ("speakers.@count") > 0
7575

@@ -122,7 +122,7 @@ final class NSPredicateTests: XCTestCase {
122122

123123
let now = Date()
124124

125-
let predicate: FetchRequest.Predicate = (#keyPath(EventObject.name)).compare(.matches, [.caseInsensitive], .value(.string(#"event \d"#))) && [
125+
let predicate: FetchRequest.Predicate = (#keyPath(EventObject.name)).compare(.matches, [.caseInsensitive], .attribute(.string(#"event \d"#))) && [
126126
(#keyPath(EventObject.start)) < now,
127127
("speakers.@count") > 0
128128
]
@@ -142,10 +142,10 @@ final class NSPredicateTests: XCTestCase {
142142
attributes.numbers = [0,1,2,3]
143143
attributes.strings = ["1", "2", "3"]
144144

145-
let predicate: FetchRequest.Predicate = (#keyPath(AttributesObject.string)).compare(.equalTo, .value(.null))
146-
&& (#keyPath(AttributesObject.data)).compare(.notEqualTo, .value(.null))
147-
&& (#keyPath(AttributesObject.numbers)).compare(.contains, .value(.int16(1)))
148-
&& (#keyPath(AttributesObject.strings)).compare(.contains, .value(.string("1")))
145+
let predicate: FetchRequest.Predicate = (#keyPath(AttributesObject.string)).compare(.equalTo, .attribute(.null))
146+
&& (#keyPath(AttributesObject.data)).compare(.notEqualTo, .attribute(.null))
147+
&& (#keyPath(AttributesObject.numbers)).compare(.contains, .attribute(.int16(1)))
148+
&& (#keyPath(AttributesObject.strings)).compare(.contains, .attribute(.string("1")))
149149

150150
let nsPredicate = predicate.toFoundation()
151151

Tests/CoreModelTests/PredicateTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ final class PredicateTests: XCTestCase {
1313

1414
func testDescription() {
1515

16-
XCTAssertEqual((.keyPath("name") == .value(.string("Coleman"))).description, "name == \"Coleman\"")
17-
XCTAssertEqual(((.keyPath("name") != .value(.null)) as FetchRequest.Predicate).description, "name != nil")
18-
XCTAssertEqual((!(.keyPath("name") == .value(.null))).description, "NOT name == nil")
16+
XCTAssertEqual((.keyPath("name") == .attribute(.string("Coleman"))).description, "name == \"Coleman\"")
17+
XCTAssertEqual(((.keyPath("name") != .attribute(.null)) as FetchRequest.Predicate).description, "name != nil")
18+
XCTAssertEqual((!(.keyPath("name") == .attribute(.null))).description, "NOT name == nil")
1919
XCTAssertEqual(("isValid" == false).description, "isValid == false")
2020
}
2121

2222
func testPredicate1() {
2323

2424
let predicate: FetchRequest.Predicate = "id" > Int64(0)
2525
&& "id" != Int64(99)
26-
&& "name".compare(.beginsWith, .value(.string("C")))
27-
&& "name".compare(.contains, [.diacriticInsensitive, .caseInsensitive], .value(.string("COLE")))
26+
&& "name".compare(.beginsWith, .attribute(.string("C")))
27+
&& "name".compare(.contains, [.diacriticInsensitive, .caseInsensitive], .attribute(.string("COLE")))
2828

2929
XCTAssertEqual(predicate.description, #"((id > 0 AND id != 99) AND name BEGINSWITH "C") AND name CONTAINS[cd] "COLE""#)
3030
}
@@ -70,7 +70,7 @@ final class PredicateTests: XCTestCase {
7070

7171
let future = Date.distantFuture
7272

73-
let predicate: FetchRequest.Predicate = ("name").compare(.matches, [.caseInsensitive], .value(.string(#"event \d"#))) && [
73+
let predicate: FetchRequest.Predicate = ("name").compare(.matches, [.caseInsensitive], .attribute(.string(#"event \d"#))) && [
7474
("start") < future,
7575
("speakers.@count") > 0
7676
]

0 commit comments

Comments
 (0)