Skip to content

Conversation

@ashwintemkar
Copy link

@ashwintemkar ashwintemkar commented Oct 10, 2025

Description of change

Fix related issue: #11637

So when a user defined junction entity exists, 0.3.26 regressed many to many persistence and inserted DEFAULT/NULL into the junction table FKs. There the root cause was metadata resolution favoring the auto generated junction metadata over the user defined junction entity when both were present (via how entityMetadatasMap was consulted).

So now I have updated DataSource lookup logic to prefer the user defined junction metadata, restoring correct FK values in the junction table and keeping entityMetadatasMap conflict-free.

Tests included

Verification

Notes

  • No behavior change for projects that use only auto generated junction tables.
  • No documentation impact expected.

Pull-Request Checklist

Summary by CodeRabbit

  • New Features

    • Improved handling of user-defined junction tables for many-to-many relations, ensuring correct metadata resolution.
  • Bug Fixes

    • Prevents errors when querying relations that use custom junction table entities.
  • Tests

    • Added functional tests covering user-defined junction table scenarios and persistence/metadata correctness.

…serve m2m inserts

0.3.26 regressed many to many persistence when a custom junction entity exists: the ORM
inserted rows into the junction table with DEFAULT/NULL FK values. Root cause was metadata
resolution selecting auto generated junction metadata over the user defined entity when both
were present, due to how entityMetadatasMap was consulted.

Change:
- DataSource: prefer user defined junction metadata over auto generated so m2m persistence
  keeps working while entityMetadatasMap still avoids conflicts

Tests:
- Added regression coverage for typeorm#11637 to ensure junction rows receive proper FK values when a
  custom junction entity exists.
- Restored the earlier typeorm#11114 regression tests to confirm repositories built from EntitySchema
  junction tables still expose relations correctly

Closes typeorm#11637
Refs typeorm#11114
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds a protected method on DataSource that deduplicates auto-generated junction entity metadatas by promoting user-defined junction metadata, remapping columns/foreign keys, updating relation references, and removing duplicate junction entries; includes tests exercising user-defined junction tables and the regression from issue #11637.

Changes

Cohort / File(s) Summary
Core deduplication logic
src/data-source/DataSource.ts
Adds protected deduplicateJunctionEntityMetadatas(entityMetadatas: EntityMetadata[]): EntityMetadata[] and associated column/foreign-key mapping logic; invokes it during buildMetadatas to replace entity metadata with deduplicated set.
Junction table test entities
test/functional/entity-metadata/entity/junction-table/entities.ts
Adds three EntitySchema definitions (User, Document, DocumentRelation) modeling a many-to-many via an explicit junction table with snake_case columns and explicit join/inverse column mappings.
Functional test coverage
test/functional/entity-metadata/entity-metadata-builder.ts
New functional test suite that initializes data sources with the junction entities and asserts repository metadata and find-with-relations do not throw for user-defined junction tables.
Issue regression test
test/github-issues/11637/issue-11637.ts
Adds regression test reproducing #11637: persists many-to-many via a user-defined junction entity and asserts junction rows contain correct foreign keys and relations load.

Sequence Diagram(s)

sequenceDiagram
    participant DS as DataSource
    participant EMB as EntityMetadataBuilder
    participant DED as deduplicateJunctionEntityMetadatas()

    DS->>EMB: buildMetadatas()
    EMB-->>DS: returns entityMetadatas (auto-generated + user-defined)
    DS->>DED: deduplicateJunctionEntityMetadatas(entityMetadatas)
    DED->>DED: identify duplicate junction metadatas
    DED->>DED: promote user-defined junction metadata
    DED->>DED: remap columns & foreign keys to user metadata
    DED->>DED: update relation metadata to reference promoted metadata
    DED->>DED: remove superseded auto-generated junction metadatas
    DED-->>DS: deduplicated entityMetadatas
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

comp: relations, need review help, size-m

Suggested reviewers

  • alumni
  • sgarner
  • gioboa

Poem

🐰
I hopped through metadatas, neat and spry,
Found duplicates hiding, gave them a try.
I stitched columns and keys in a tidy line,
So junction rows hold what they should: keys that align.
Rejoice — no more NULLs — a rabbit's small rhyme.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: prefers user defined junction metadata over auto generated for many to many and added regression tests" accurately summarizes the main changes in the changeset. The title clearly identifies the primary fix (preferring user-defined over auto-generated junction metadata) and explicitly mentions the addition of regression tests, which directly corresponds to the code changes: the new deduplicateJunctionEntityMetadatas method in DataSource.ts and the three new test files added to validate the fix. The title is specific, concise, and sufficiently clear that a developer reviewing pull request history would understand this addresses a many-to-many relationship regression with junction entity metadata precedence.
Linked Issues Check ✅ Passed The code changes directly address all primary coding objectives from linked issue #11637. The implementation adds a deduplicateJunctionEntityMetadatas method to DataSource.ts that promotes user-defined junction entity metadata over auto-generated synthetic metadata by remapping columns and foreign keys, updating relations, and removing duplicate metadata entries—directly fixing the regression where junction table inserts were using DEFAULT/NULL values instead of proper foreign key relationships. The PR also includes three new test files that validate the fix: a regression test specifically for issue #11637 that confirms junction tables contain correct FK values, an entity-metadata-builder functional test that verifies metadata deduplication prevents duplicate relations, and EntitySchema-based entity definitions supporting the test scenarios. These changes collectively restore pre-0.3.26 behavior and ensure framework preference for user-defined junction metadata while preserving compatibility with auto-generated junction tables.
Out of Scope Changes Check ✅ Passed All code changes in this pull request are directly in scope for fixing issue #11637 and implementing the stated PR objectives. The modification to src/data-source/DataSource.ts implements the core deduplication logic to resolve the junction metadata precedence issue, while the three new test files (entity-metadata-builder.ts, junction-table/entities.ts, and issue-11637.ts) provide regression and functional testing to validate the fix and ensure EntitySchema-based junction tables function correctly. No changes to documentation, public API surface beyond an internal protected method, or unrelated features are present. The PR scope remains tightly focused on resolving the many-to-many relationship regression with user-defined junction entities and validating the solution through comprehensive testing.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 11, 2025

commit: e2d1b65

@coveralls
Copy link

coveralls commented Oct 11, 2025

Coverage Status

coverage: 80.919% (+0.003%) from 80.916%
when pulling e2d1b65 on ashwintemkar:fix/junction-metadata-precedence-11637
into 7de850e on typeorm:master.

@ashwintemkar ashwintemkar marked this pull request as ready for review October 13, 2025 15:24
@ashwintemkar ashwintemkar marked this pull request as draft October 13, 2025 15:25
@ashwintemkar
Copy link
Author

@sgarner @ragrag pardon me for tagging,

I have opened this Draft PR to check whether anything more is needed out of the fix I have put up, please let me know if this is ready for review, as the core problem is fixed

Copy link
Contributor

@ragrag ragrag left a comment

Choose a reason for hiding this comment

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

overall i think this approach looks cogent and should behave more consistently than the previous implementation 🙏🏻

@ashwintemkar
Copy link
Author

Thanks for pointing those out! @ragrag , I pulled the stray junction-table regression test back out and added inline comments explaining why we prefer user-defined metadata over the synthetic junction entry. Let me know if you’d like any more detail there.

@ragrag
Copy link
Contributor

ragrag commented Oct 15, 2025

Thanks for pointing those out! @ragrag , I pulled the stray junction-table regression test back out and added inline comments explaining why we prefer user-defined metadata over the synthetic junction entry. Let me know if you’d like any more detail there.

sorry i meant to only remove the enabledDrivers: ['sqlite'] part (the line my comment was on initially) we should keep the tests for sure, rest lgtm!

@ashwintemkar
Copy link
Author

so dropping the explicit enabledDrivers override I hit the Postgres-only failure I saw constraint "... does not exist", which meant the auto-generated junction metadata was still hanging around alongside the user-defined entity. That’s why I extended EntityMetadataBuilder

We now merge the auto-generated junction metadata back into the user-defined entity, reuse its columns, and drop the duplicate entry. Without that, Postgres tries to create the same PK/FKs twice and the regression resurfaces.
Relations that were wired to the synthetic junction metadata are re-pointed to the user entity so many-to-many inserts keep their FK values vet_specialty, feed_resources_categories, etc.
Existing coverage for the #11114 scenario is still passing, and the new regression test for #11637 passes on Postgres with the fix npm test -- --grep "junction".

So the earlier logic from commit 95baf98 is still there, and the new builder hook just finishes the job by reconciling the actual metadata objects.

@ragrag
Copy link
Contributor

ragrag commented Oct 15, 2025

We now merge the auto-generated junction metadata back into the user-defined entity, reuse its columns, and drop the duplicate entry. Without that, Postgres tries to create the same PK/FKs twice and the regression resurfaces. Relations that were wired to the synthetic junction metadata are re-pointed to the user entity so many-to-many inserts keep their FK values vet_specialty, feed_resources_categories, etc. Existing coverage for the #11114 scenario is still passing, and the new regression test for #11637 passes on Postgres with the fix npm test -- --grep "junction".

could we perhaps do this merging once for all conflicting junction metadatas during metaDatasMap registration (or merging such tables before the metadata generation step)? that would be a much simpler single change, you think something like that could work?

e.g:

entityMetadatasMap: new Map(
    withDeduplicatedJunctionTables(entityMetadatas).map((metadata) => [metadata.target, metadata]),
),

also it could be worth investigating if typeorm already has some internal method for metadata merging or table merging that we could instead of mergeWithUserDefinedJunctions

@ragrag
Copy link
Contributor

ragrag commented Oct 15, 2025

throwing other ideas i feel that might solve this more straightforwardly. originally entityMetaDatasMap was introduced in f12a95c to make metadata lookups faster, we could potentially explore some variants that would maintain the fast lookups that have potential to work:

1 - use the previous metadata logic that loops over entityMetadatas and adding a cache on top in favor of entityMetadatasMap, so the initial lookup will have similar result pre 3.13 and consequent lookups will be resolved from the cache to the correct metadata. essentially having entityMetadatasMap but building it in a lazy fashion (imho the most straight forward)

2 - in findMetadata, if entity found in entityMetadatasMap is a junction entity (or an entity we know is a potential duplicate junction entity from some precalculation we did) do not return it and, fallback to old logic that loops over entityMetadatas from the commit above

3 - gathering a set of user defined tables that have a junction pair in entityMetadatas, in findMetadata check if target is part of that set and if that's the case fallback to old logic that loops over entityMetadatas from the commit above

4- wild idea and not sure if it'll work but constructing entityMetadatasMap from the reverse of entityMetadatas, my guess is that junction and virtual tables are pushed to the end of entityMetadatas causing the map overriding to happen

theoretically all of those solutions should maintain the same behaviour as before the regression introduced in 3.13 (from the commit above) and would avoid potentially error prone metadata merging, let me know what you think

@ashwintemkar
Copy link
Author

Hey @ragrag thanks for the detailed suggestions!

I moved the reconciliation to metadata registration:

  1. we now deduplicate the auto-generated junction metadata before entityMetadatasMap is built
  2. reuse the junction FK definitions on the user entity
  3. repoint the relations to the user metadata.

That keeps the map fast, avoids the extra builder hook, and resolves the Postgres constraint/index failures.

Happy to iterate further!! if you spot anything else.

also for your reference

throwing other ideas i feel that might solve this more straightforwardly. originally entityMetaDatasMap was introduced in f12a95c to make metadata lookups faster, we could potentially explore some variants that would maintain the fast lookups that have potential to work:

1 - use the previous metadata logic that loops over entityMetadatas and adding a cache on top in favor of entityMetadatasMap, so the initial lookup will have similar result pre 3.13 and consequent lookups will be resolved from the cache to the correct metadata. essentially having entityMetadatasMap but building it in a lazy fashion (imho the most straight forward)

2 - in findMetadata, if entity found in entityMetadatasMap is a junction entity (or an entity we know is a potential duplicate junction entity from some precalculation we did) do not return it and, fallback to old logic that loops over entityMetadatas from the commit above

3 - gathering a set of user defined tables that have a junction pair in entityMetadatas, in findMetadata check if target is part of that set and if that's the case fallback to old logic that loops over entityMetadatas from the commit above

4- wild idea and not sure if it'll work but constructing entityMetadatasMap from the reverse of entityMetadatas, my guess is that junction and virtual tables are pushed to the end of entityMetadatas causing the map overriding to happen

theoretically all of those solutions should maintain the same behaviour as before the regression introduced in 3.13 (from the commit above) and would avoid potentially error prone metadata merging, let me know what you think

A lazy cache (option 1) and the targeted fallbacks (options 2 & 3) still leave duplicate metadata in entityMetadatas, so the schema builder continued to create duplicate PK/FK definitions in Postgres.

Reversing the insertion order (option 4) depends on incidental ordering and still risks selecting the wrong metadata.

Hence the new deduplication step keeps the fast lookups from entityMetadatasMap, avoids metadata merging inside the builder, and gives us a single, predictable place to reconcile the two metadata sources.

@ragrag
Copy link
Contributor

ragrag commented Oct 16, 2025

Amazing work!

Hence the new deduplication step keeps the fast lookups from entityMetadatasMap, avoids metadata merging inside the builder, and gives us a single, predictable place to reconcile the two metadata sources.

just wondering if we still need all the other changes done now (the change in findMetadata, and buildMetadata), shouldn't the metadata merging with deduplicateJunctionEntityMetadatas alone pre-registration with the old way fix the issue?

btw did you manage to find any typeorm internals that do the metadata merging?

@ashwintemkar
Copy link
Author

ashwintemkar commented Oct 16, 2025

just wondering if we still need all the other changes done now (the change in findMetadata, and buildMetadata),

so I initally thought that the ideas were good when this #11720 (comment) was suggested but then I thought deduplicateJunctionEntityMetadatas would bring the edge here.

shouldn't the metadata merging with deduplicateJunctionEntityMetadatas alone pre-registration with the old way fix the issue?

and you’re right that the heavy lifting happens in the new deduplicateJunctionEntityMetadatas step. I reverted the tweaks to findMetadata and to the entityMetadatasMap construction so they’re back to the exact logic we had with the synthetic junction metadata removed before the map is built, those extra guard rails aren’t needed.

btw did you manage to find any typeorm internals that do the metadata merging?

And also I didn’t find an existing helper in TypeORM that performs this reconciliation. I checked the metadata builders and schema helpers, but nothing exposed the junction metadata we needed to reuse.

so currently
deduplicateJunctionEntityMetadatas runs right after buildEntityMetadatas, strips out the auto generated junction entries, and rewires any relations that were pointing at them and because the duplicates are gone, findMetadata’s original implementation is returning the correct user defined entity without the additional filtering.

for now I think this can go in as the original findMetadata logic behaves exactly like pre‑3.13, and both the #11114 functional coverage and the #11637 regression test pass npm test -- --grep "entity-metadata-builder" and npm test -- --grep "junction"

@ragrag
Copy link
Contributor

ragrag commented Oct 16, 2025

so currently deduplicateJunctionEntityMetadatas runs right after buildEntityMetadatas, strips out the auto generated junction entries, and rewires any relations that were pointing at them and because the duplicates are gone, findMetadata’s original implementation is returning the correct user defined entity without the additional filtering.

coool, seems like this covers all the edges so looks good from my side, perhaps other maintainers are more familiar in the matter of metadata merging

@ashwintemkar
Copy link
Author

ashwintemkar commented Oct 16, 2025

so currently deduplicateJunctionEntityMetadatas runs right after buildEntityMetadatas, strips out the auto generated junction entries, and rewires any relations that were pointing at them and because the duplicates are gone, findMetadata’s original implementation is returning the correct user defined entity without the additional filtering.

coool, seems like this covers all the edges so looks good from my side, perhaps other maintainers are more familiar in the matter of metadata merging

alright so should I mark this ready for review?, I mean turn into actual PR from draft?

@ragrag
Copy link
Contributor

ragrag commented Oct 16, 2025

alright so should I mark this ready for review?, I mean turn into actual PR from draft?

sure feel free to!

@ashwintemkar ashwintemkar marked this pull request as ready for review October 16, 2025 17:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 181154a and b06d688.

📒 Files selected for processing (4)
  • src/data-source/DataSource.ts (3 hunks)
  • test/functional/entity-metadata/entity-metadata-builder.ts (1 hunks)
  • test/functional/entity-metadata/entity/junction-table/entities.ts (1 hunks)
  • test/github-issues/11637/issue-11637.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
test/functional/entity-metadata/entity-metadata-builder.ts (3)
src/data-source/DataSource.ts (1)
  • DataSource (58-944)
test/utils/test-utils.ts (3)
  • createTestingConnections (388-482)
  • reloadTestingDatabases (504-509)
  • closeTestingConnections (487-499)
test/functional/entity-metadata/entity/junction-table/entities.ts (1)
  • documentRelationEntitySchema (65-87)
test/github-issues/11637/issue-11637.ts (4)
src/decorator/relations/ManyToMany.ts (1)
  • ManyToMany (33-77)
src/decorator/relations/JoinTable.ts (1)
  • JoinTable (30-56)
src/data-source/DataSource.ts (1)
  • DataSource (58-944)
test/utils/test-utils.ts (3)
  • createTestingConnections (388-482)
  • reloadTestingDatabases (504-509)
  • closeTestingConnections (487-499)
src/data-source/DataSource.ts (4)
src/metadata/EntityMetadata.ts (1)
  • EntityMetadata (30-1139)
src/metadata/ColumnMetadata.ts (1)
  • ColumnMetadata (18-1003)
src/error/TypeORMError.ts (1)
  • TypeORMError (1-17)
src/metadata/ForeignKeyMetadata.ts (1)
  • ForeignKeyMetadata (11-127)
test/functional/entity-metadata/entity/junction-table/entities.ts (2)
src/driver/mongodb/typings.ts (1)
  • Document (3147-3147)
src/index.ts (1)
  • EntitySchema (182-182)

… (referencedColumn / relationMetadata) and leave any user-defined column options untouched.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/data-source/DataSource.ts (1)

739-754: Consider adding an inline comment for the mapColumn helper.

While the logic is clear, a brief comment explaining that this helper maps junction columns to their corresponding user-entity columns (by database name) would improve maintainability.

Apply this diff to add an inline comment:

+        // Maps a junction column to its corresponding column in the user entity by database name
         const mapColumn = (column: ColumnMetadata): ColumnMetadata => {
             const match = userMetadata.columns.find(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b06d688 and 064bef3.

📒 Files selected for processing (1)
  • src/data-source/DataSource.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/data-source/DataSource.ts (4)
src/metadata/EntityMetadata.ts (1)
  • EntityMetadata (30-1139)
src/metadata/ColumnMetadata.ts (1)
  • ColumnMetadata (18-1003)
src/error/TypeORMError.ts (1)
  • TypeORMError (1-17)
src/metadata/ForeignKeyMetadata.ts (1)
  • ForeignKeyMetadata (11-127)
🔇 Additional comments (8)
src/data-source/DataSource.ts (8)

19-20: LGTM: Required imports for metadata handling.

The new imports for ColumnMetadata and ForeignKeyMetadata are necessary for the junction deduplication logic that follows.


714-719: LGTM: Clear documentation of purpose.

The method signature and comment clearly explain the deduplication strategy and why it's needed (favoring user-defined metadata over auto-generated).


723-730: LGTM: Efficient early-return optimization.

Building a map of user metadata by table path and returning early when no user entities exist is a good optimization.


750-751: LGTM: Correctly preserves user-defined column options.

The code now only copies the wiring properties (referencedColumn, relationMetadata) without overwriting user-configured structural column options (isNullable, type, length, etc.). This properly addresses the past review concern about clobbering user-provided settings.


756-808: LGTM: Comprehensive promotion logic.

The promotion sequence correctly:

  • Maps owner/inverse columns via the helper
  • Clones foreign keys with updated entity references
  • Deduplicates FKs using signature-based comparison (column names + referenced table)
  • Updates user metadata flags to mark it as a junction
  • Registers the deduplicated FKs

The signature generation (lines 781-785, 791-795) properly uses sorted column names to ensure order-independence.


810-829: LGTM: Relation update scope is comprehensive.

Iterating all entity metadatas to find and update relations that reference the synthetic junction is thorough. While this has O(n²) complexity in the number of entities and relations, it's necessary for correctness and acceptable for typical applications.

The relation update correctly:

  • Filters out old foreign keys from the synthetic junction
  • Registers new foreign keys mapped to the user entity
  • Re-registers join columns with the mapped columns
  • Updates the junction reference to point to the user metadata

834-848: LGTM: Main deduplication loop and filtering.

The loop correctly identifies synthetic junctions with matching user entities and promotes them, then filters out the superseded synthetic metadata. The Set-based removal tracking ensures clean deduplication.


871-876: LGTM: Proper integration into metadata build pipeline.

Invoking deduplicateJunctionEntityMetadatas immediately after building entity metadatas and before populating entityMetadatasMap ensures:

  • Synthetic duplicates are removed before fast-lookup structures are built
  • Downstream validation and schema sync see only the promoted user metadata
  • No duplicate PK/FK constraints are created

This addresses the core regression from issue #11637.

@ashwintemkar
Copy link
Author

@ragrag any idea by when this is merging in ?

@ashwintemkar
Copy link
Author

ashwintemkar commented Oct 24, 2025

Hi @gioboa do my changes check out? and can this pr be merged in?

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

Thanks for your help @ashwintemkar
LGTM ✅

@gioboa gioboa requested review from alumni and sgarner October 24, 2025 18:30
@ashwintemkar
Copy link
Author

Hey @gioboa and @ragrag , since @sgarner and @alumni might take some time to review according to their availability, as all checks have passed, I had a small request, as maintainers could y'all add a hacktoberfest accepted tag onto this PR, since I have participated in the hacktoberfest, I think adding the tag might count as contribution, just want to see if that works.

@gioboa gioboa added the hacktoberfest-accepted label hacktoberfest label Oct 31, 2025
@ashwintemkar
Copy link
Author

thanks a tonne @gioboa

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

PR Code Suggestions ✨

CategorySuggestion                                                                                                                                    Impact
General
Include cascade options in signature

Improve the foreign key signature generation by including onDelete, onUpdate,
and deferrable properties. This ensures more accurate deduplication of foreign
keys.

src/data-source/DataSource.ts [779-787]

 const existingForeignKeySignatures = new Set(
     userMetadata.foreignKeys.map((foreignKey) => {
         const columnSignature = foreignKey.columns
             .map((column) => column.databaseName)
             .sort()
             .join(",")
-        return `${columnSignature}->${foreignKey.referencedEntityMetadata.tablePath}`
+        return `${columnSignature}->${foreignKey.referencedEntityMetadata.tablePath}:${foreignKey.onDelete}:${foreignKey.onUpdate}:${foreignKey.deferrable}`
     }),
 )
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the foreign key signature is incomplete, which could lead to incorrectly identifying foreign keys as duplicates. Including onDelete, onUpdate, and deferrable in the signature makes the deduplication logic more accurate and robust.

Medium
Validate entity compatibility before promotion

Add a validation check to ensure a user-defined entity is not already a special
type (e.g., 'closure') before promoting it to a 'junction' table. This prevents
potential misconfigurations.

src/data-source/DataSource.ts [803-808]

+if (userMetadata.tableMetadataArgs.type && userMetadata.tableMetadataArgs.type !== "junction") {
+    throw new TypeORMError(
+        `Cannot promote entity "${userMetadata.name}" to junction table: entity is already defined as type "${userMetadata.tableMetadataArgs.type}".`,
+    )
+}
 userMetadata.tableMetadataArgs.type = "junction"
 userMetadata.tableType = "junction"
 userMetadata.isJunction = true
 userMetadata.ownerColumns = ownerColumns
 userMetadata.inverseColumns = inverseColumns
 userMetadata.foreignKeys.push(...foreignKeysToRegister)
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion proposes adding a defensive check before modifying an entity's metadata type. While this could prevent misconfiguration, an entity that matches a junction table's path but has another special type (like 'closure') is an unlikely edge case. The improvement is minor.

Low
Possible issue
Avoid mutating shared column metadata

Avoid mutating shared column metadata by cloning the ColumnMetadata object
within the mapColumn function before modifying its properties. This prevents
potential side effects.

src/data-source/DataSource.ts [739-754]

 const mapColumn = (column: ColumnMetadata): ColumnMetadata => {
     const match = userMetadata.columns.find(
         (candidate) =>
             candidate.databaseName === column.databaseName,
     )
     if (!match) {
         throw new TypeORMError(
             `Column "${column.databaseName}" referenced by the join table "${junctionMetadata.tableName}" was not found in the user-defined entity "${userMetadata.name}".`,
         )
     }
 
-    match.referencedColumn = column.referencedColumn
-    match.relationMetadata = column.relationMetadata
+    const clonedColumn = Object.create(Object.getPrototypeOf(match))
+    Object.assign(clonedColumn, match)
+    clonedColumn.referencedColumn = column.referencedColumn
+    clonedColumn.relationMetadata = column.relationMetadata
 
-    return match
+    return clonedColumn
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that mutating the match object, which is an element of userMetadata.columns, can lead to unintended side effects. Cloning the object before mutation is a good practice for robustness and prevents potential bugs that are hard to trace.

Low
  • More

gioboa
gioboa previously requested changes Jan 7, 2026
Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

The test file must have .test.ts extension to be considered.

@ashwintemkar
Copy link
Author

The test file must have .test.ts extension to be considered.

sure @gioboa I will look into the changes needed

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

PR Code Suggestions ✨

No code suggestions found for the PR.

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

Code Review by Qodo (Alpha)

🐞 Bugs (2) 📘 Rule Violations (0) 📎 Requirement Gaps (0) 💡 Suggestions (0)

Grey Divider


Action Required

1. Missing indices transfer in junction deduplication 🐞 Bug
Description
• The deduplicateJunctionEntityMetadatas method transfers foreign keys and columns from
  auto-generated junction metadata to user-defined metadata, but does not transfer the indices
• Junction tables automatically get two indices created (one for owner columns, one for inverse
  columns) by JunctionEntityMetadataBuilder
• These indices are critical for query performance on many-to-many relations and are used by
  EntityMetadataBuilder when registering join columns via junctionEntityMetadata.ownIndices[0].columns
  and junctionEntityMetadata.ownIndices[1].columns
• When a user-defined entity replaces the auto-generated junction, these indices are lost,
  potentially causing severe performance degradation on many-to-many queries

performance

Code

src/data-source/DataSource.ts[R803-808]

+            userMetadata.tableMetadataArgs.type = "junction"
+            userMetadata.tableType = "junction"
+            userMetadata.isJunction = true
+            userMetadata.ownerColumns = ownerColumns
+            userMetadata.inverseColumns = inverseColumns
+            userMetadata.foreignKeys.push(...foreignKeysToRegister)
Evidence
The code transfers ownerColumns, inverseColumns, and foreignKeys to userMetadata but does not
transfer ownIndices. Junction tables require two indices for proper query performance, and these are
used by the relation registration process.

src/metadata-builder/JunctionEntityMetadataBuilder.ts[266-284]
src/metadata-builder/EntityMetadataBuilder.ts[304-307]

Agent Prompt
## Issue Description
The deduplicateJunctionEntityMetadatas method does not transfer the automatically-generated indices from the junction metadata to the user-defined metadata. Junction tables require two indices (one for owner columns, one for inverse columns) for proper query performance, and these indices are used by the relation registration process.

## Issue Context
Junction tables automatically get two indices created by JunctionEntityMetadataBuilder. These indices are critical because:
1. They improve query performance on many-to-many relations
2. They are used by EntityMetadataBuilder.registerJoinColumns() which accesses junctionEntityMetadata.ownIndices[0].columns and junctionEntityMetadata.ownIndices[1].columns
3. Without these indices, many-to-many queries will have degraded performance

## Fix Focus Areas
- src/data-source/DataSource.ts[803-808] - Add code to transfer indices after transferring foreign keys
- src/data-source/DataSource.ts[739-754] - Consider if the mapColumn function needs to be used to map index columns to user metadata columns

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


2. Column metadata mutation without validation 🐞 Bug
Description
• The mapColumn function unconditionally overwrites ColumnMetadata.referencedColumn and
  ColumnMetadata.relationMetadata properties on user-defined columns
• These properties are critical for column behavior and are used extensively in
  ColumnMetadata.getEntityValue() and other methods
• If user-defined columns already have these properties set (e.g., if the user defined their own
  relations on the junction entity), this code will overwrite them without validation
• This could break existing column configurations and relation mappings in the user-defined entity

correctness

Code

src/data-source/DataSource.ts[R750-751]

+                match.referencedColumn = column.referencedColumn
+                match.relationMetadata = column.relationMetadata
Evidence
The code directly assigns to match.referencedColumn and match.relationMetadata without checking if
these properties are already set. This could overwrite existing metadata if the user-defined
junction entity has its own relation definitions.

src/metadata/ColumnMetadata.ts[298-301]
src/metadata/ColumnMetadata.ts[44-48]
src/data-source/DataSource.ts[750-751]

Agent Prompt
## Issue Description
The mapColumn function unconditionally overwrites ColumnMetadata.referencedColumn and ColumnMetadata.relationMetadata properties without checking if they're already set. This could break existing column configurations if the user-defined junction entity has its own relation definitions.

## Issue Context
The referencedColumn and relationMetadata properties are critical for column behavior:
- referencedColumn: Points to the column this foreign key references
- relationMetadata: Points to the relation this column belongs to
- Both are used extensively in ColumnMetadata.getEntityValue() and other methods
- Overwriting them could break existing functionality

## Fix Focus Areas
- src/data-source/DataSource.ts[750-751] - Add validation before overwriting these properties
- Consider: Should we skip assignment if already set, merge metadata, or throw an error on conflict?

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


Grey Divider

Qodo Logo

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

I fixed the test file name.

@ashwintemkar
Copy link
Author

I fixed the test file name.

okay, so is this pr now waiting only for approvals right?

@gioboa gioboa dismissed their stale review January 15, 2026 10:26

Fixed.

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

okay, so is this pr now waiting only for approvals right?

That's correct. the PR now is ready with a green pipeline 💪

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Many-to-many relationships broken when user-defined junction entity exists (0.3.26 regression)

4 participants