Skip to content

Support the predicate expressions CoreData can represent - #35

Merged
colemancda merged 23 commits into
masterfrom
feature/predicate
Aug 16, 2026
Merged

colemancda merged 23 commits into
masterfrom
feature/predicate

Conversation

@colemancda

@colemancda colemancda commented Aug 16, 2026

Copy link
Copy Markdown
Member

Follow-up to #34 (which merged the base Foundation.Predicate conversion), extending both the predicate model and the in-memory evaluator to cover the expressions CoreData can represent.

Arithmetic

  • New FetchRequest.Predicate.ArithmeticExpression (add/subtract/multiply/divide/modulus) as an Expression case, with Codable support. Its raw values are the corresponding NSExpression function names, so CoreDataModel bridges them directly via NSExpression(forFunction:).
  • In-memory evaluation: integer operands stay integral, so division truncates exactly as Swift's / and NSExpression's divide:by: both do (7 / 2 is 3); division or remainder by zero, overflowing division, floating-point remainder, and non-numeric operands resolve to no match rather than trapping.
  • #Predicate converts +, -, *, /, %, and unary minus (lowered to * -1, as NSExpression has no negation function).

Collections, aggregates and ranges

  • allSatisfy { ... } and contains(where:) { ... } convert to ALL/ANY modifier comparisons. Those nodes bind their own element variable, so conversions now thread a context mapping each predicate variable to its key path.
  • min() / max() convert to @min / @max key path operators.
  • Half-open ranges (18..<30) convert alongside closed ones, lowered to comparisons since BETWEEN isn't evaluatable.

Regex

  • contains(regex) converts to a MATCHES comparison. The macro wraps regexes in PredicateExpressions.PredicateRegex, which retains the source pattern, so regex literals, Regex(String), and RegexBuilder regexes all convert. The pattern is padded with .* because MATCHES matches the whole value rather than a substring, matching Foundation's own conversion. Requires macOS 15 / iOS 18, where those types are available.

Relationship key path traversal (bug fix)

Key paths that traverse a relationship (events.name) previously resolved to nothing in the in-memory evaluator, so every ALL/ANY predicate silently matched zero objects while the identical predicate returned the correct rows through CoreData. The evaluator now resolves to-one and to-many relationships against an object index: FetchRequest.evaluate(_:) builds the index from the objects it is given, and InMemoryStorage supplies every entity's objects so relationships resolve.

Tests

  • ArithmeticExpressionTests: construction, description, Codable round-trip, evaluation semantics, fetch request evaluation.
  • FoundationPredicateTests: every supported expression shape, converted-tree assertions, in-memory filtering, and error paths.
  • KeyPathTraversalTests: to-one and to-many traversal, ALL/ANY, unresolved relationships, InMemoryStorage fetches, and the same traversals built with #Predicate.
  • CoreDataModelTests: end-to-end CoreData fetches for arithmetic, ALL/ANY, and regex predicates built both from #Predicate and the CoreModel API — including a check that empty to-many ALL is vacuously true in CoreData and that the in-memory evaluator agrees on the same objects.

161 tests pass, including the Embedded WebAssembly build (the object index is built without dynamic casting, which Embedded Swift forbids).

@github-code-quality

Copy link
Copy Markdown

Code Coverage Overview

Languages: Swift

Swift / code-coverage/llvm-cov

The overall coverage in commit 5b11022 in the feature/predicate branch is 94%. The coverage in commit ea30b8b in the master branch is 95%.

Show a code coverage summary of the most impacted files.
File master ea30b8b feature/predicate 5b11022 +/-
Sources/CoreMod.../Evaluate.swift 96% 95% -1%
Sources/CoreMod...ryStorage.swift 92% 92% 0%
Sources/CoreDat...valuation.swift 100% 100% 0%
Sources/CoreDat...Predicate.swift 100% 100% 0%
Sources/CoreMod...valuation.swift 100% 100% 0%
Sources/CoreMod...xpression.swift 100% 100% 0%
Sources/CoreMod...Predicate.swift 85% 89% +4%
Sources/CoreMod...xpression.swift 0% 100% +100%

@colemancda
colemancda merged commit 65503f5 into master Aug 16, 2026
31 of 32 checks passed
@colemancda
colemancda deleted the feature/predicate branch August 16, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant