drizzle-team / drizzle-team/drizzle-orm

[BUG]: `drizzle-kit pull` treats domain tables with exactly two foreign keys as junction tables

Open
#6,253 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.