fix: Guid non-unique indexes + portable Nullable<T> index record loss#4
Merged
Merged
Conversation
Two independent index bugs, plus regression tests. Embedded — non-unique secondary index over a Guid-keyed table threw (NullReferenceException at insert, InvalidOperationException at comparer build). A non-unique index key is Slots(field, primaryKey); with a Guid PK that composite contains a Guid slot, which had no comparer: - TypeEngine only built comparers for all-primitive or bare-Guid types, so the composite got a null KeyComparer -> NRE. Add DataTypeUtils.IsAllComparable (primitive-or-Guid, recursive) and widen the gate (IndexerPersist stays primitive-only — Guid has none). - ComparerHelper declared the shared `cmp` temp only when a char/byte[]/ decimal/string member was present, so a composite whose members are Guid/ int/DateTime referenced an undeclared variable and the comparer expression would not compile. Always declare `cmp`. Remote/portable — a non-unique index over a Nullable<T> field SILENTLY dropped main-table rows (100 inserts -> ~2 survive, no exception). The server opens tables portable, so a Nullable<T> field arrives as a nested single-slot Slots<T>; the index built a malformed key Slots(Slots<T>, pk) -> null KeyComparer -> NRE swallowed server-side -> the write batch was dropped. General remote Query on the field additionally threw "The type (T) is not primitive". - SlotAccessor: treat a single-slot Slots<T> (the portable Nullable<T> representation) as its inner T in NormalizeStorageType / NormalizeStorageValue (null wrapper -> default(T)) and BuildValueNormalizer; add TryGetPortableNullableType. - TableIndexManager.GetMemberType: reconstruct Nullable<T> from the portable slot so the remote field codec stays symmetric with the client. Tests: GuidPkNonUniqueReproTests, RemoteInsertReproTests, RemoteNullableQueryTests. Full suite 360 passing; live-server stress run reports zero errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Two independent index bugs, plus regression tests.
Embedded — non-unique secondary index over a Guid-keyed table threw (NullReferenceException at insert, InvalidOperationException at comparer build). A non-unique index key is Slots(field, primaryKey); with a Guid PK that composite contains a Guid slot, which had no comparer:
cmptemp only when a char/byte[]/ decimal/string member was present, so a composite whose members are Guid/ int/DateTime referenced an undeclared variable and the comparer expression would not compile. Always declarecmp.Remote/portable — a non-unique index over a Nullable field SILENTLY dropped main-table rows (100 inserts -> ~2 survive, no exception). The server opens tables portable, so a Nullable field arrives as a nested single-slot Slots; the index built a malformed key Slots(Slots, pk) -> null KeyComparer -> NRE swallowed server-side -> the write batch was dropped. General remote Query on the field additionally threw "The type (T) is not primitive".
representation) as its inner T in NormalizeStorageType / NormalizeStorageValue
(null wrapper -> default(T)) and BuildValueNormalizer; add
TryGetPortableNullableType.
Tests: GuidPkNonUniqueReproTests, RemoteInsertReproTests, RemoteNullableQueryTests. Full suite 360 passing; live-server stress run reports zero errors.