-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
fix: prefers user defined junction metadata over auto generated for many to many and added regression tests #11720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix: prefers user defined junction metadata over auto generated for many to many and added regression tests #11720
Conversation
…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
WalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
commit: |
ragrag
left a comment
There was a problem hiding this 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 🙏🏻
|
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 |
|
so dropping the explicit 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. 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. |
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 |
|
throwing other ideas i feel that might solve this more straightforwardly. originally 1 - use the previous metadata logic that loops over 2 - in 3 - gathering a set of user defined tables that have a junction pair in 4- wild idea and not sure if it'll work but constructing 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 |
|
Hey @ragrag thanks for the detailed suggestions! I moved the reconciliation to metadata registration:
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
A lazy cache (option 1) and the targeted fallbacks (options 2 & 3) still leave duplicate metadata in 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 |
|
Amazing work!
just wondering if we still need all the other changes done now (the change in btw did you manage to find any typeorm internals that do the metadata merging? |
so I initally thought that the ideas were good when this #11720 (comment) was suggested but then I thought
and you’re right that the heavy lifting happens in the new
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 for now I think this can go in as the original |
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? |
sure feel free to! |
There was a problem hiding this 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
📒 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.
There was a problem hiding this 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 themapColumnhelper.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
📒 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
ColumnMetadataandForeignKeyMetadataare 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
deduplicateJunctionEntityMetadatasimmediately after building entity metadatas and before populatingentityMetadatasMapensures:
- 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.
|
@ragrag any idea by when this is merging in ? |
|
Hi @gioboa do my changes check out? and can this pr be merged in? |
gioboa
left a comment
There was a problem hiding this 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 ✅
|
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. |
|
thanks a tonne @gioboa |
PR Code Suggestions ✨
|
||||||||||||||
gioboa
left a comment
There was a problem hiding this 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.
sure @gioboa I will look into the changes needed |
PR Code Suggestions ✨No code suggestions found for the PR. |
Code Review by Qodo (Alpha)
1. Missing indices transfer in junction deduplication
|
gioboa
left a comment
There was a problem hiding this 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.
okay, so is this pr now waiting only for approvals right? |
gioboa
left a comment
There was a problem hiding this 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 💪
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/NULLinto 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 howentityMetadatasMapwas consulted).So now I have updated
DataSourcelookup logic to prefer the user defined junction metadata, restoring correct FK values in the junction table and keepingentityMetadatasMapconflict-free.Tests included
EntitySchemabased junction tables still expose relations correctly.Verification
npm test -- --grep "junction"on Postgres.Notes
Pull-Request Checklist
masterbranchSummary by CodeRabbit
New Features
Bug Fixes
Tests