Composite Attribute Support - #36
Merged
Merged
Conversation
Code Coverage OverviewLanguages: Swift Swift / code-coverage/llvm-covThe overall coverage in commit e1be582 in the Show a code coverage summary of the most impacted files.
Updated |
…ttribute # Conflicts: # Sources/CoreModel/Predicate/Evaluate.swift
…ttribute # Conflicts: # Sources/CoreModel/Predicate/Evaluate.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #11.
Adds composite attributes — a named attribute whose value is a dictionary of named sub-attributes — modeled on Core Data's
NSCompositeAttributeDescription(iOS 17 / macOS 14).Previously a struct-valued property had to be hand-flattened into a single scalar. The test model shows what that cost:
locationwas stored as"34.51446,-89.15318", opaque to the store, unqueryable, unsortable, and needing a bespoke string grammar plus parser per type.Campground.LocationCoordinatesandScheduleare now real composites, so the existing end-to-end CoreData assertions became composite coverage for free.Design
AttributeValue.composite([PropertyKey: AttributeValue])AttributeType.composite([Attribute])Entity.attributes[CodingKeys: AttributeType])Neither enum needs
indirect—Array/Dictionaryalready break the layout cycle, which avoids a box allocation per value and keepsswift_allocBoxout of Embedded builds.Two constraints Core Data enforces at runtime are unrepresentable here: an element can never be a relationship (
AttributeTypehas no relationship case), and the element tree is a finite value, so it can't be recursive.Source-breaking changes
AttributeTypelosesStringRawRepresentableandCaseIterable(an associated value precludes both). Replaced byscalarRawValue/init?(scalarRawValue:)/scalarCases. Deliberately not re-added as a lossyRawRepresentable, since.compositewould round-trip toniland silently corrupt schemas.NSManagedObjectModel.init(model:)is nowthrows, propagating toNSEntityDescription.init(entity:)andPersistentContainerStorage.init. Composites can't be represented below macOS 14, and this is the only point that holds the whole model and can fail before any data is written.CoreModel-SQLite,CoreModel-MongoDB) will need updating: non-exhaustive switches over both enums, therawValue/allCasesreplacements above, andSortTerm.propertymay now carry a dotted path. MongoDB maps.compositeto an embedded document; SQLite to a JSON column withjson_extract.Wire format for scalars is unchanged —
AttributeTypehas hand-writtenCodableso{"id":"name","type":"string"}still encodes byte-for-byte, asserted by a test. Only models that actually use composites get the new nested shape.Coding
CompositeAttribute/CompositeAttributeEncodable/CompositeAttributeDecodablerefine the existing protocols, so composites flow throughModelData.encode/decodeand theOptionalconformances with no new overloads and no change to the macro's encode/decode codegen.@Attribute(LocationCoordinates.attributeType)also works as an escape hatch with no macro involvement, and is covered by a test.Predicates and sorting
PredicateValue.init?(data:keyPath:)walksPredicateKeyPathinto composite values, solocation.latitude > 30and sorting byofficeHours.startwork in the pure-Swift evaluator. This is load-bearing beyond the in-memory store:FetchRequest.Predicate.evaluate(with:)is also Core Data's custom-function fallback path, which would otherwise return wrong results.No new
SortTermcase — a dottedpropertyis the key path, matchingNSSortDescriptor(key:)and MongoDB's sort spec. A property whose name literally contains a dot still resolves directly, so existing models are unaffected.Comparison semantics needed no changes: equality is recursive dictionary equality, and composites are uncomparable for ordering (
orderreturnsnil), consistent with.data/.uuid.Also fixes a latent debug trap: the
Foundation.Predicateroot variable converts to an empty key path, which previously reachedPropertyKey(rawValue: "")and tripped its assertion.Verified rather than assumed
SWIFT_EMBEDDED=1, wasm SDK).location.latitudeinto a fetch predicate —sqliteElementPredicateproves it against a real store rather than trusting the documentation.NSInvalidArgumentException, "Core Data provided atomic stores do not support composite attributes". Since that aborts the process and can't be caught from Swift, it's documented as a precondition onNSManagedObjectModel.init(model:), with a warning on the in-memory test helper so nobody adds a composite toAllTypes.Notes
amenitiesstays@Attribute(.string)—[Amenity]is variable-length, and a composite is a fixed set of named elements. It also keeps a regression test that the flat-string path still works alongside composites.CoreDataTestsandBatchInsertTestsmove to macOS 14 / iOS 17, since their model now contains composites. All pure-CoreModel tests still run everywhere.AttributeDecodableconformances gained one case each rather than being collapsed onto a shared helper — that refactor would change overflow behavior from trapping toniland belongs in its own PR.203 tests pass (up from 141). Builds clean with macros on, macros off, and under Embedded Swift.