db: stop push re-creating every composite key on every run
`bun db:push` planned 32 statements against a database that already matched the
schema, and stopped on a "do you want to truncate screens?" prompt that answering
could not resolve — the same question came back next run. Root cause found and
fixed rather than worked around.
drizzle-kit mis-diffs named composite unique CONSTRAINTS. It reads one back,
compares it against a schema declaring the identical name, columns and order,
decides they differ, and emits DROP + ADD. Fifteen of those, forever. Reproduced
on a database drizzle had itself created seconds earlier, so it is not drift.
Single-column .unique() is diffed correctly; only unique('name').on(a, b) is
affected. Unique indexes go through a different code path and are stable, so all
fifteen are now uniqueIndex.
A unique index enforces exactly what the constraint did — verified, a duplicate
insert still fails on uq_screens_user_name — and onConflictDoUpdate accepts it as
an arbiter. It cannot be a foreign-key target, but nothing here targets a
composite key; checked before converting.
Separately, user_integrations_server_integration_id_server_integrations_id_fk is
65 characters and Postgres truncates identifiers at 63, so drizzle compared its
generated name against the stored, truncated one and re-created the FK every run.
Declared explicitly as fk_user_integrations_server_integration.
Measured on a scratch database, pushing twice each time:
before 32 statements, interactive prompt
after uniqueIndex 4
after FK fix 2
The two that remain are a composite primaryKey with the same bug and no index
form to escape to — music_now_playing re-creates pk_music_now_playing every push.
Silent, no prompt even with rows, data unaffected, and naming it explicitly does
not help. Documented as expected.
The conversion itself was tested against populated tables, since that is what the
real database will do: no prompt, and all rows survived.
Docs rewritten in src/databases/CLAUDE.md — the rules committed an hour ago
described the broken behaviour and would have been wrong the moment this landed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -100,10 +100,11 @@ database and alters it directly. **`drizzle-kit migrate` has never been run here
|
||||
history was deleted because it had drifted from the real schema. Treat the schema code, not those
|
||||
files, as the source of truth.
|
||||
|
||||
**`push` is interactive and asks the same question on every run** — it plans 16 statements against a
|
||||
database that already matches the schema, because drizzle-kit mis-diffs named composite unique
|
||||
constraints. **Never answer "Yes, truncate the table", and never delete a constraint from the schema to
|
||||
silence it.** Read `src/databases/CLAUDE.md` → "push is interactive" before running it.
|
||||
**Declare multi-column uniqueness as `uniqueIndex('uq_…').on(a, b)`, never `unique('uq_…').on(a, b)`** —
|
||||
drizzle-kit mis-diffs named composite unique *constraints* and re-creates them on every push, which used
|
||||
to stop `db:push` on an unanswerable truncate prompt. Same for any foreign key whose generated name would
|
||||
exceed Postgres's 63-character identifier limit: name it explicitly. See `src/databases/CLAUDE.md` →
|
||||
"Composite keys" before adding either.
|
||||
|
||||
## Security Model
|
||||
|
||||
|
||||
Reference in New Issue
Block a user