Skip to content

fix: resolve entity-typed primary keys in id-based where expressions#282

Merged
zantvoort merged 1 commit into
mainfrom
fix/entity-typed-pk-id-resolution
Jul 17, 2026
Merged

fix: resolve entity-typed primary keys in id-based where expressions#282
zantvoort merged 1 commit into
mainfrom
fix/entity-typed-pk-id-resolution

Conversation

@zantvoort

@zantvoort zantvoort commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Problem

An id-based lookup (findAllById, whereId) on an entity whose primary key is itself an entity resolved the key values by type. When the key entity's table appears more than once in the root's join graph, the type-based lookup is ambiguous and the query fails with:

SqlTemplateException: Cannot uniquely identify object in expression: multiple matches found for type Owner.
Ensure the expression resolves to a single, unambiguous metamodel path.

The canonical shape is a junction row keyed by an entity whose table is also reachable through the junction's second foreign key:

public record OwnerPrimaryPet(
        @PK(generation = NONE) @FK("owner_id") Owner owner,
        @FK("pet_id") Pet pet                                // pet.owner reaches OWNER again
) implements Entity<Owner> {}

The write set's AndFetch variants (#269, #270) hit this on every fetch-back of such a junction: WriteSetImpl.fetch() calls findAllById with the propagated key entities, so insertAndFetch/upsertAndFetch of an affected graph always fails. Seen in production as a batch-sync failure on every batch containing new users.

Cause

whereId builds an ObjectExpression without a metamodel, so QueryModelImpl.getMetamodel re-derives the target from the runtime value type. For Data/Ref values it only tries root-type assignability and graph-unique type resolution before throwing; the primary-key fallback (isPrimaryKeyValue) further down is only reachable for scalar values. An entity-typed key value therefore never resolves against the primary-key path, even though the call site's contract (ID = Owner) is exact.

Fix

In getMetamodel, when type-based resolution is ambiguous, fall back to the root's primary-key path if the value type matches the root's primary-key type. This mirrors the existing scalar behavior (an Integer id against an Integer-keyed root resolves to the primary key even when other integer columns exist) and leaves every currently-resolving case untouched; only the previously-throwing case changes. Genuinely ambiguous expressions (a non-key type appearing twice) still throw.

Tests

  • EntityRepositoryIntegrationTest#testFindAllByIdWithEntityTypedPkWhoseTableIsReachedTwice: repository-only repro; the insert succeeds, the id-based read failed before this fix.
  • WriteSetIntegrationTest#testInsertAndFetchJunctionWithEntityTypedPrimaryKey: write-set repro via insertAndFetch of an OwnerPrimaryPet graph, matching the production failure path.
  • New test model OwnerPrimaryPet + owner_primary_pet schema (no seed rows).

Both tests fail with the exact error above without the fix. Full storm-core suite: 2307 tests, 0 failures.

Relates to #269.

An id-based lookup (findAllById, whereId) on an entity whose primary key
is itself an entity resolved the key values by type. When the key
entity's table appears more than once in the root's join graph (a
junction row whose second foreign key reaches the same table again), the
type-based lookup is ambiguous and the query failed with "Cannot
uniquely identify object in expression". The write set's AndFetch
variants hit this on every fetch-back of such a junction.

Object-expression resolution now falls back to the root's primary key
path when the value type matches the primary key type, mirroring the
existing primary-key fallback for scalar values.
@zantvoort
zantvoort merged commit 09c5c4f into main Jul 17, 2026
7 checks passed
@zantvoort
zantvoort deleted the fix/entity-typed-pk-id-resolution branch July 17, 2026 16:15
@zantvoort zantvoort added this to the 1.13.0 milestone Jul 17, 2026
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