fix(metadata-builder): keep the STI discriminator index on entities with embeddeds by brmk · Pull Request #12841 · typeorm/typeorm · GitHub
Skip to content

fix(metadata-builder): keep the STI discriminator index on entities with embeddeds - #12841

Open
brmk wants to merge 1 commit into
typeorm:masterfrom
brmk:fix/sti-discriminator-index-with-embedded
Open

fix(metadata-builder): keep the STI discriminator index on entities with embeddeds#12841
brmk wants to merge 1 commit into
typeorm:masterfrom
brmk:fix/sti-discriminator-index-with-embedded

Conversation

@brmk

@brmk brmk commented Sep 5, 2026

Copy link
Copy Markdown

Description of change

The index that EntityMetadataBuilder.createKeysForTableInheritance() creates on an STI discriminator column is silently dropped when the entity has at least one embedded column and at least one generated column.

computeEntityMetadataStep2() derives indices from ownIndices:

entityMetadata.indices = entityMetadata.embeddeds.reduce(
    (indices, embedded) => indices.concat(embedded.indicesFromTree),
    entityMetadata.ownIndices,
)

With no embeddeds, reduce returns the initial value by reference, so indices is ownIndices and the later indices.push(...) in createKeysForTableInheritance() also lands in ownIndices. With one or more embeddeds, concat returns a new array, so the pushed index lives only on that detached copy. build() then re-runs computeEntityMetadataStep2() for every entity that has a generated column, which reassigns indices from ownIndices and discards the auto-created index. No error, no warning — the table is simply created without the index.

All three conditions are needed: STI base, at least one embedded column, at least one generated column. A @PrimaryGeneratedColumn() id supplies the third, so in practice any framework that puts an embedded column on its base entity loses every STI discriminator index (see the issue for the Vendure case and its production impact).

The fix registers the auto-created index in ownIndices, which is what every computeEntityMetadataStep2() run preserves. Because indices may be the very same array as ownIndices, it is rebuilt with concat rather than pushed to, so the index is never registered twice.

Behavior that is deliberately unchanged:

  • the existing isDiscriminatorColumnAlreadyIndexed guard still short-circuits when the user has their own @Index() on the discriminator column, so no duplicate index appears for projects that already worked around this by hand;
  • entities without embeddeds keep exactly the one index they have today.

The same code shape exists on the v0 branch, so 0.3.x is affected as well. This PR targets master; happy to open the equivalent backport if you want it there.

Verification. Ran locally against better-sqlite3 (ormconfig.json limited to that driver): the full suite passes, and the new test file fails on master without the source change (expected [] to have a length of 1) and passes with it. The behavior is decided at the metadata layer, before any driver is involved, so the outcome is driver-independent; the new test enables the same driver list as the closest existing test (test/github-issues/10496) minus spanner, and I did not run the non-sqlite drivers locally.

New tests in test/github-issues/12840:

  • STI base with an embedded column and a generated id — the discriminator index exists, both in EntityMetadata.indices and on the created table;
  • same shape plus a user-defined @Index() on the discriminator column — exactly one index, keeping the user's name (dedup guard intact);
  • STI base without embedded columns — still exactly one index (no regression).

Closes #12840

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links a relevant issue using a closing keyword:
    Fixes #NNNN, Closes #NNNN, or Resolves #NNNN
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md) — N/A, this restores documented/intended behavior and changes no public API

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 5, 2026

Copy link
Copy Markdown

…ith embeddeds

createKeysForTableInheritance pushed the auto-created discriminator index onto
entityMetadata.indices. That array is derived from ownIndices and is recomputed by
computeEntityMetadataStep2, which build() runs again for every entity that has a
generated column, so the index was discarded. It only survived when the entity had
no embeddeds, because reduce then returns ownIndices by reference and the push
mutated ownIndices as well.

Register the index in ownIndices instead, so it survives the recomputation. Since
indices can be the very same array as ownIndices, it is rebuilt with concat rather
than pushed to, to avoid registering the index twice. The existing guard against
indexing an already indexed discriminator column is unaffected.

Regression coverage lives in
test/functional/table-inheritance/single-table/discriminator-index-with-embedded.

Closes typeorm#12840
@brmk
brmk force-pushed the fix/sti-discriminator-index-with-embedded branch from e37aa93 to 61b02c7 Compare September 5, 2026 20:59
@brmk

brmk commented Sep 5, 2026

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

linked-issue PR references an issue

Development

Successfully merging this pull request may close these issues.

Auto-created STI discriminator index is silently dropped when the entity has an embedded column

1 participant