Skip to content

fix: Guid non-unique indexes + portable Nullable<T> index record loss#4

Merged
OmidID merged 1 commit into
mainfrom
fix/guid-nonunique-and-portable-nullable-index
Jul 7, 2026
Merged

fix: Guid non-unique indexes + portable Nullable<T> index record loss#4
OmidID merged 1 commit into
mainfrom
fix/guid-nonunique-and-portable-nullable-index

Conversation

@OmidID

@OmidID OmidID commented Jul 7, 2026

Copy link
Copy Markdown
Owner

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 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".

  • SlotAccessor: treat a single-slot Slots (the portable Nullable
    representation) as its inner T in NormalizeStorageType / NormalizeStorageValue
    (null wrapper -> default(T)) and BuildValueNormalizer; add
    TryGetPortableNullableType.
  • TableIndexManager.GetMemberType: reconstruct Nullable 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.

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>
@OmidID OmidID merged commit b41badb into main Jul 7, 2026
1 check passed
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