drizzle-team / drizzle-team/drizzle-orm

[BUG]: drizzle-kit pull drops single-column PK/FK constraint names from schema.ts (kept nameExplicit in snapshot) — pull→generate demands rename-or-create hints for every legacy-named constraint

Open
#6,025 0 comments 0 reactions 1 assignee Claimed by @AleksandrSherman View on GitHub
bug drizzle/kit
Dominant language
TypeScript
Stars
35.8k
Forks
1.6k
Avg merge
2d 7h
Merged PRs (30d)
4

Description

### What version of `drizzle-orm` are you using?

1.0.0-rc.4

### What version of `drizzle-kit` are you using?

1.0.0-rc.4

### Other packages

_No response_

### Describe the Bug

`drizzle-kit pull` writes the real PK/FK constraint names into `snapshot.json` with `"nameExplicit": true`, but renders **single-column** primary keys and **single-column, non-self** foreign keys in the generated `schema.ts` as name-less shorthand (`.primaryKey()` / `.references(() => ...)`). The explicit names are lost in the TypeScript output.

Consequence: on any database whose constraint names differ from drizzle's derived defaults (very common — PG's own `__fkey` convention, or DMS/pgloader-migrated databases with `idx_39129_PRIMARY`-style names), the immediately following `drizzle-kit generate` sees every such constraint as a delete+create pair and demands a rename-or-create hint for each one, exiting with code 2. On our production database that was 143 prompts right after a clean `pull --init` baseline — making the documented "pull once, then code-first" adoption path unusable without manual intervention.

Uniques, indexes, checks, composite PKs, and multi-column/self-referencing FKs are unaffected (they are emitted with explicit names at table level).

Root cause (as of the `beta` branch, confirmed present in the published rc.4 `bin.cjs`):

1. `src/dialects/postgres/introspect.ts` marks every pulled PK/FK `nameExplicit: true` unconditionally.
2. `src/dialects/postgres/typescript.ts` deliberately routes single-column non-self FKs to inline `.references()` — the filter keeps only `it.columns.length > 1 || isSelf(it)` for the name-capable table-level `foreignKey({ ..., name })` renderer, with the comment `// Andrii: I switched this one off until we will get custom names in .references()`. Single-column PKs are rendered inline `.primaryKey()` with `// TODO: or pk has non-default name`.
3. The differ matches entities by a composite key that includes the name, and `preserveEntityNames` in `src/dialects/utils.ts` only reconciles names when `mode === 'push' || !x.nameExplicit` — so in `generate` mode every explicitly-named snapshot entity (i.e. all of them, after a pull) is skipped, and each name mismatch becomes a rename-or-create conflict.

Notably, `drizzle-kit/tests/postgres/pull.test.ts` asserts empty `generateStatements` after introspection, and its "issue 5525" fixture even contains a legacy PK named `idx_19612_sqlite_autoindex_constructor_1` plus `_fkey`-convention FKs — so pull→generate idempotency looks like an intended invariant that this breaks.

Workaround we're using in production: post-process the pulled `schema.ts`, moving each single-column PK to table-level `primaryKey({ columns: [...], name: '' })` and each FK to `foreignKey({ columns, foreignColumns, name: '' })` (names taken from the snapshot's `nameExplicit: true` entries). After that enrichment, `generate` reports "No schema changes, nothing to migrate". Re-running `pull` destroys the enrichment, so it can only be run into a scratch directory.

Related: #1549 (the pre-v1 form of this loop, open since 2023), #4115 (closed as fixed-in-beta — the fix covered only multi-column/self FKs).

### Steps to reproduce

```sql
CREATE TABLE country (id serial NOT NULL, name text);
ALTER TABLE country ADD CONSTRAINT "idx_39129_PRIMARY" PRIMARY KEY (id);
CREATE TABLE city (id serial PRIMARY KEY, country_id int NOT NULL);
ALTER TABLE city ADD CONSTRAINT city_country_id_fkey FOREIGN KEY (country_id) REFERENCES country(id);
```

1. `drizzle-kit pull` → `snapshot.json` records `idx_39129_PRIMARY` and `city_country_id_fkey` with `"nameExplicit": true`; `schema.ts` renders `.primaryKey()` and `.references(() => country.id)` — names gone.
2. `drizzle-kit generate` (no schema edits at all) →

```
missing_hints: unresolved decisions
Rename or create — primary key public.country.country_pkey
Rename or create — foreign key public.city.city_country_id_country_id_fkey
Re-run with --hints ''. Exit code 2.
```

Expected: `generate` immediately after `pull` produces "No schema changes, nothing to migrate" (pull→generate idempotency), either by emitting the explicit names in `schema.ts` (table-level `primaryKey({name})` / `foreignKey({name})`, as already done for composite PKs and multi-column FKs) or by letting `preserveEntityNames` reconcile explicitly-named snapshot entities in `generate` mode when the TS side carries no explicit name.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.