drizzle-team / drizzle-team/drizzle-orm
[BUG]: `drizzle-kit pull` treats domain tables with exactly two foreign keys as junction tables
- Dominant language
- TypeScript
- Stars
- 35.8k
- Forks
- 1.6k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 4
Description
### Report hasn't been filed before.
- [x] I have verified that the bug I'm about to report hasn't been filed before.
### What version of `drizzle-orm` are you using?
1.0.0-rc.5-ab785fc
### What version of `drizzle-kit` are you using?
1.0.0-rc.5-ab785fc
### Other packages
_No response_
### Describe the Bug
This report concerns the initial classification of any table with exactly two foreign keys as a many-to-many junction.
Related to #6100
### What version of `drizzle-orm` are you using?
`1.0.0-rc.5-ab785fc`
### What version of `drizzle-kit` are you using?
`1.0.0-rc.5-ab785fc`
### What database are you using?
PostgreSQL
### Describe the bug
`drizzle-kit pull` appears to classify every table with exactly two foreign-key constraints as a many-to-many junction table.
This happens even when the table is a first-class domain entity with:
- Its own primary key
- Several non-foreign-key business columns
- Other tables referencing it
Instead of generating direct relations for the table’s foreign keys, drizzle-kit generates many-to-many relation between the two referenced tables through the domain table.
The direct relations from the domain entity to the referenced tables are omitted.
### Minimal reproduction
PostgreSQL schema:
```sql
create table account (
id uuid primary key
);
create table category (
id uuid primary key
);
create table transaction_record (
id uuid primary key,
account_id uuid not null references account(id),
category_id uuid references category(id),
amount numeric not null,
status text not null,
description text,
created_at timestamptz not null default now()
);
```
Then run:
```sh
drizzle-kit pull
```
`transaction_record` has exactly two foreign keys, but it is not a junction table. It represents a transaction with its own identity and business data.
### Actual behavior
drizzle-kit infers an `account <-> category` many-to-many relationship through `transaction_record`:
```ts
account: {
categories: r.many.category({
from: r.account.id.through(r.transaction_record.account_id),
to: r.category.id.through(r.transaction_record.category_id),
}),
},
```
The relations from `transaction_record` to `account` and `category` are not generated.
In a larger schema, this can also leave generated reverse relations without matching direct relations. Importing the generated relation graph may then fail during application startup with an error such as:
```text
not enough data provided to build the relation -
"from"/"to" are not defined, and no reverse relation ... was found
```
### Expected behavior
The foreign keys should produce direct relations:
```ts
transaction_record: {
account: r.one.account({
from: r.transaction_record.account_id,
to: r.account.id,
}),
category: r.one.category({
from: r.transaction_record.category_id,
to: r.category.id,
}),
},
```
The referenced tables should receive ordinary reverse collections:
```ts
account: {
transaction_records: r.many.transaction_record(),
},
category: {
transaction_records: r.many.transaction_record(),
},
```
### Apparent cause
The presence of exactly two foreign keys is not enough to establish that a table is a junction.
A domain entity can legitimately reference exactly two other tables. Primary keys, unique constraints, non-FK columns, and the general table structure need to be considered.
Even with additional heuristics, the semantics can remain ambiguous. Generating the direct foreign-key relations unconditionally would be safer than replacing them with an inferred through-relation.
### Impact
- Generated relational-query metadata does not represent the database model.
- Direct `one` relations are missing from the generated API.
- Incorrect many-to-many relations are exposed.
- Reverse relations can become unresolvable.
- `defineRelations()` may throw during application startup.
- Projects using `drizzle-kit pull` need to patch the generated `relations.ts` after every regeneration.
Contributor guide
Research direction
Reproduce the issue with the PostgreSQL schema in the report and run `drizzle-kit pull`. Trace the relation inference used during pull, focusing on why any table with exactly two foreign keys is treated as a junction table. Done means domain tables retain direct relations to both referenced tables, with ordinary reverse collections and no invalid many-to-many relation or startup failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, typescript
- Domain
- database
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100