Skip to content

fix: prevent composite primary key collisions during hydration - #12905

Open
IbrahimHafez1 wants to merge 2 commits into
typeorm:masterfrom
IbrahimHafez1:fix/composite-key-hydration-collisions
Open

IbrahimHafez1 wants to merge 2 commits into
typeorm:masterfrom
IbrahimHafez1:fix/composite-key-hydration-collisions

Conversation

@IbrahimHafez1

@IbrahimHafez1 IbrahimHafez1 commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Description of change

Fixes #12902.

Composite primary keys such as ("a_b", "c") and ("a", "b_c") both become a_b_c in the hydration grouping map. This drops a root entity and can combine children belonging to different entities.

Encode normalized composite values as a JSON tuple so component boundaries remain distinct. Preserve the single-column grouping behavior and existing binary/object normalization; convert native bigint values to strings before JSON encoding.

Two regression tests cover distinct roots with their own children and colliding joined child keys, including repeated underscores. Both failed before the change. The linked issue includes a standalone reproduction.

Validation on Windows / Node 22.17.1:

  • TypeScript compilation passed.
  • Combined run with the three independent tree/hydration fixes: 3,081 passing, 179 pending, using SQL.js and better-sqlite3. Other database engines were not run.
  • Changed-file formatting and lint checks passed (lint reported existing warnings).
  • Grouping sanity checks covered string, bigint, binary, object, and quote-containing values.

Performance: grouping remains linear, but collision-safe composite encoding costs more than delimiter joining. A local microbenchmark of the actual grouping method over 100,000 rows / 20,000 entities (15 alternating measured iterations after warmup) measured composite medians of 14.8 ms before and 21.3 ms after. Single-column medians were 7.0 ms and 7.8 ms. These are local grouping-only measurements, excluding SQL and entity construction; this is a correctness fix, not a speedup.

Pull-Request Checklist

  • Based on current master at f279fd1367f24ad108a1b11cf833f1620274088d.
  • Linked the relevant issue and checked related issues/PRs.
  • Added regression tests.
  • Documentation: N/A; restores existing behavior without a public API change.

Review follow-up

Addressed the non-finite key finding in ebec2f7.

Confirmed against PostgreSQL 15.6: three composite keys containing NaN, Infinity, and -Infinity hydrated as one entity before this follow-up. Non-finite numbers now use nested tuple values, distinguishing them from each other, null, and literal strings during JSON encoding. Single-column string coercion is preserved.

Added a PostgreSQL regression that verifies distinct root entities, their own descendants, and all three joined children of a common parent. It failed before the change and passes afterward. Also added the issue reference to the original functional suite.

Validation: TypeScript compilation passed; focused hydration suites passed (3 tests, including PostgreSQL); broader query-builder tests passed with SQL.js/better-sqlite3 (434 passing, 6 pending). ESLint reported zero errors and 38 existing warnings. Formatting and whitespace checks passed.

@github-actions github-actions Bot added the linked-issue PR references an issue label Sep 26, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

commit: ebec2f7

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

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

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Distinct float keys merge during loading ✓ Resolved 🐞 Bug ≡ Correctness
Description
group passes composite numeric key values directly to JSON.stringify, which serializes NaN,
Infinity, and -Infinity identically as null. PostgreSQL composite keys containing distinct
non-finite floating-point values therefore enter the same hydration group, merging separate roots or
joined children.
Code

packages/typeorm/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[154]

+                        : JSON.stringify(keyValues)
Evidence
The transformer groups raw query results before hydration and the new composite branch serializes
the tuple with JSON, whose non-finite numeric values all become null. PostgreSQL explicitly
supports floating-point column types, its hydration path returns those values unchanged, and the
existing PostgreSQL fixture confirms that double precision primary columns are represented as
JavaScript numbers.

packages/typeorm/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[136-154]
packages/typeorm/src/query-builder/SelectQueryBuilder.ts[3631-3646]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[151-173]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[993-1003]
packages/typeorm/test/github-issues/352/entity/Post.ts[5-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Composite primary-key grouping serializes non-finite numbers as JSON `null`, causing distinct PostgreSQL floating-point keys to collide during hydration.
## Fix Focus Areas
- packages/typeorm/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[136-154]
- packages/typeorm/test/functional/query-builder/composite-key-hydration/composite-key-hydration.test.ts[18-89]
## Recommended Fix
Before serializing a composite tuple, normalize non-finite numeric values to a collision-safe tagged representation that distinguishes `NaN`, `Infinity`, and `-Infinity`. Add a PostgreSQL regression test using a composite floating-point primary key and verify that each distinct entity and its relations hydrate separately.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


  • Author self-review: I have reviewed the code review findings, and addressed the relevant ones.

Grey Divider

Context sources
Review mode: ⚖️ Balanced: This changes core ORM hydration grouping semantics and serialization edge cases, with real data-merging risk, but the logic is localized rather than bug-dense enough to warrant redundant extended review.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit ebec2f7 ⚖️ Balanced

Results up to commit 5646793


🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)


Action required
1. Distinct float keys merge during loading 🐞 Bug ≡ Correctness
Description
group passes composite numeric key values directly to JSON.stringify, which serializes NaN,
Infinity, and -Infinity identically as null. PostgreSQL composite keys containing distinct
non-finite floating-point values therefore enter the same hydration group, merging separate roots or
joined children.
Code

packages/typeorm/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[154]

+                        : JSON.stringify(keyValues)
Evidence
The transformer groups raw query results before hydration and the new composite branch serializes
the tuple with JSON, whose non-finite numeric values all become null. PostgreSQL explicitly
supports floating-point column types, its hydration path returns those values unchanged, and the
existing PostgreSQL fixture confirms that double precision primary columns are represented as
JavaScript numbers.

packages/typeorm/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[136-154]
packages/typeorm/src/query-builder/SelectQueryBuilder.ts[3631-3646]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[151-173]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[993-1003]
packages/typeorm/test/github-issues/352/entity/Post.ts[5-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Composite primary-key grouping serializes non-finite numbers as JSON `null`, causing distinct PostgreSQL floating-point keys to collide during hydration.

## Fix Focus Areas
- packages/typeorm/src/query-builder/transformer/RawSqlResultsToEntityTransformer.ts[136-154]
- packages/typeorm/test/functional/query-builder/composite-key-hydration/composite-key-hydration.test.ts[18-89]

## Recommended Fix
Before serializing a composite tuple, normalize non-finite numeric values to a collision-safe tagged representation that distinguishes `NaN`, `Infinity`, and `-Infinity`. Add a PostgreSQL regression test using a composite floating-point primary key and verify that each distinct entity and its relations hydrate separately.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Context sources
Review mode: ⚖️ Balanced: This changes core ORM hydration grouping semantics and composite-key encoding, so it carries meaningful behavioral risk despite the localized three-file diff.

Grey Divider

Qodo Logo

@IbrahimHafez1

Copy link
Copy Markdown
Contributor Author

Addressed the non-finite key finding in ebec2f7.

Confirmed against PostgreSQL 15.6: three composite keys containing NaN, Infinity, and -Infinity hydrated as one entity before this follow-up. Non-finite numbers now use nested tuple values, distinguishing them from each other, null, and literal strings during JSON encoding. Single-column string coercion is preserved.

Added a PostgreSQL regression that verifies distinct root entities, their own descendants, and all three joined children of a common parent. It failed before the change and passes afterward. Also added the issue reference to the original functional suite.

Validation: TypeScript compilation passed; focused hydration suites passed (3 tests, including PostgreSQL); broader query-builder tests passed with SQL.js/better-sqlite3 (434 passing, 6 pending). ESLint reported zero errors and 38 existing warnings. Formatting and whitespace checks passed.

@Cprakhar Cprakhar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IbrahimHafez1 Your solution looks solid to me.

This branch has not been deployed

No deployments
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.

Composite string primary keys containing underscores collapse distinct entities during hydration

2 participants