Skip to content

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

@danielfrankcom

Description

@danielfrankcom

Issue description

TypeORM 0.3.26 appears to have changed behavior for many-to-many relationships when user-defined junction entities exist. The relationship data is not being persisted correctly, with junction table rows containing NULL values instead of the expected foreign key relationships.

Expected Behavior

In TypeORM <=0.3.25, junction table should be populated with proper foreign key values:

INSERT INTO "vet_specialty"("vet_id", "specialty_name") VALUES ($1, $2), ($3, $4) -- PARAMETERS: [1,"dogs",1,"cats"]

Junction table contains relationship data, queries return related entities.

> npm install typeorm@0.3.25 --no-save && npm test

changed 1 package, and audited 140 packages in 1s

found 0 vulnerabilities

> type-orm-repro@1.0.0 test
> ts-node test.ts

query: SELECT version()
query: SELECT * FROM current_schema()
query: SELECT "Specialty"."name" AS "Specialty_name" FROM "specialty" "Specialty" WHERE "Specialty"."name" IN ($1, $2) -- PARAMETERS: ["dogs","cats"]
query: START TRANSACTION
query: INSERT INTO "specialty"("name") VALUES ($1), ($2) -- PARAMETERS: ["dogs","cats"]
query: COMMIT
query: SELECT "Specialty"."name" AS "Specialty_name" FROM "specialty" "Specialty" WHERE "Specialty"."name" IN ($1, $2) -- PARAMETERS: ["dogs","cats"]
query: START TRANSACTION
query: INSERT INTO "vet"("name") VALUES ($1) RETURNING "id" -- PARAMETERS: ["Carlos Salazar"]
query: INSERT INTO "vet_specialty"("vet_id", "specialty_name") VALUES ($1, $2), ($3, $4) -- PARAMETERS: [1,"dogs",1,"cats"]
query: COMMIT
query: SELECT DISTINCT "distinctAlias"."Vet_id" AS "ids_Vet_id" FROM (SELECT "Vet"."id" AS "Vet_id", "Vet"."name" AS "Vet_name", "Vet__Vet_specialties"."name" AS "Vet__Vet_specialties_name" FROM "vet" "Vet" LEFT JOIN "vet_specialty" "Vet_Vet__Vet_specialties" ON "Vet_Vet__Vet_specialties"."vet_id"="Vet"."id" LEFT JOIN "specialty" "Vet__Vet_specialties" ON "Vet__Vet_specialties"."name"="Vet_Vet__Vet_specialties"."specialty_name" WHERE (("Vet"."id" = $1))) "distinctAlias" ORDER BY "Vet_id" ASC LIMIT 1 -- PARAMETERS: [1]
query: SELECT "Vet"."id" AS "Vet_id", "Vet"."name" AS "Vet_name", "Vet__Vet_specialties"."name" AS "Vet__Vet_specialties_name" FROM "vet" "Vet" LEFT JOIN "vet_specialty" "Vet_Vet__Vet_specialties" ON "Vet_Vet__Vet_specialties"."vet_id"="Vet"."id" LEFT JOIN "specialty" "Vet__Vet_specialties" ON "Vet__Vet_specialties"."name"="Vet_Vet__Vet_specialties"."specialty_name" WHERE ( (("Vet"."id" = $1)) ) AND ( "Vet"."id" IN (1) ) -- PARAMETERS: [1]
Vet specialties count: 2
Expected: 2, Actual: 2

> psql -d postgres -c "SELECT * FROM vet_specialty"
 id | vet_id | specialty_name
----+--------+----------------
  1 |      1 | dogs
  2 |      1 | cats
(2 rows)

Actual Behavior

In TypeORM >=0.3.26, junction table gets rows with NULL/DEFAULT values:

INSERT INTO "vet_specialty"("vet_id", "specialty_name") VALUES (DEFAULT, DEFAULT), (DEFAULT, DEFAULT) RETURNING "id"

Junction table contains empty rows, queries return no related entities.

> npm install typeorm@0.3.26 --no-save && npm test

changed 1 package, and audited 140 packages in 933ms

found 0 vulnerabilities

> type-orm-repro@1.0.0 test
> ts-node test.ts

query: SELECT version()
query: SELECT * FROM current_schema()
query: SELECT "Specialty"."name" AS "Specialty_name" FROM "specialty" "Specialty" WHERE "Specialty"."name" IN ($1, $2) -- PARAMETERS: ["dogs","cats"]
query: START TRANSACTION
query: INSERT INTO "specialty"("name") VALUES ($1), ($2) -- PARAMETERS: ["dogs","cats"]
query: COMMIT
query: SELECT "Specialty"."name" AS "Specialty_name" FROM "specialty" "Specialty" WHERE "Specialty"."name" IN ($1, $2) -- PARAMETERS: ["dogs","cats"]
query: START TRANSACTION
query: INSERT INTO "vet"("name") VALUES ($1) RETURNING "id" -- PARAMETERS: ["Carlos Salazar"]
query: INSERT INTO "vet_specialty"("vet_id", "specialty_name") VALUES (DEFAULT, DEFAULT), (DEFAULT, DEFAULT) RETURNING "id"
query: COMMIT
query: SELECT DISTINCT "distinctAlias"."Vet_id" AS "ids_Vet_id" FROM (SELECT "Vet"."id" AS "Vet_id", "Vet"."name" AS "Vet_name", "Vet__Vet_specialties"."name" AS "Vet__Vet_specialties_name" FROM "vet" "Vet" LEFT JOIN "vet_specialty" "Vet_Vet__Vet_specialties" ON "Vet_Vet__Vet_specialties"."vet_id"="Vet"."id" LEFT JOIN "specialty" "Vet__Vet_specialties" ON "Vet__Vet_specialties"."name"="Vet_Vet__Vet_specialties"."specialty_name" WHERE (("Vet"."id" = $1))) "distinctAlias" ORDER BY "Vet_id" ASC LIMIT 1 -- PARAMETERS: [1]
query: SELECT "Vet"."id" AS "Vet_id", "Vet"."name" AS "Vet_name", "Vet__Vet_specialties"."name" AS "Vet__Vet_specialties_name" FROM "vet" "Vet" LEFT JOIN "vet_specialty" "Vet_Vet__Vet_specialties" ON "Vet_Vet__Vet_specialties"."vet_id"="Vet"."id" LEFT JOIN "specialty" "Vet__Vet_specialties" ON "Vet__Vet_specialties"."name"="Vet_Vet__Vet_specialties"."specialty_name" WHERE ( (("Vet"."id" = $1)) ) AND ( "Vet"."id" IN (1) ) -- PARAMETERS: [1]
Vet specialties count: 0
Expected: 2, Actual: 0

> psql -d postgres -c "SELECT * FROM vet_specialty"
 id | vet_id | specialty_name
----+--------+----------------
  1 |        |
  2 |        |
(2 rows)

Steps to reproduce

Minimal reproducer available at danielfrankcom/typeorm-repro.

The reproducer includes a simple many-to-many relationship with a user-defined junction entity. Running the test with TypeORM 0.3.25 vs 0.3.26 shows the different behavior.

If the vet_specialty table is created in migration.sql but the VetSpecialty entity is not defined in entities.ts, the junction table works and produces the expected result. The existence of the junction table entity seems to cause the difference in behavior.

My Environment

Dependency Version
Operating System MacOS
Node.js version v22.18.0
Typescript version 5.9.2
TypeORM version 0.3.26

Additional Context

This behavior change appears to be related to PR #11114, which addressed duplicate metadata issues for user-defined junction tables. The change may have affected how junction metadata is created when explicit junction entities are present.

Relevant Database Driver(s)

  • aurora-mysql
  • aurora-postgres
  • better-sqlite3
  • cockroachdb
  • cordova
  • expo
  • mongodb
  • mysql
  • nativescript
  • oracle
  • postgres
  • react-native
  • sap
  • spanner
  • sqlite
  • sqlite-abstract
  • sqljs
  • sqlserver

Are you willing to resolve this issue by submitting a Pull Request?

No, I don’t have the time and I’m okay to wait for the community / maintainers to resolve this issue.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions