drizzle-team / drizzle-team/drizzle-orm

[BUG]: drizzle-kit push (pg): composite-PK column order introspected non-deterministically — PK dropped/recreated on random pushes

Open Beginner friendly
#6,078 1 comment 0 reactions 0 assignees View on GitHub
bug bug/fixed-in-beta 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?

0.45.2

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

0.31.10 — and the root cause is still present in `drizzle-kit/src/serializer/pgSerializer.ts` on `main`

### Describe the Bug

`drizzle-kit push` against Postgres drops and re-adds a composite primary key on *some* pushes even when nothing in the schema changed — and **which** runs/databases are affected is non-deterministic (it can flip after unrelated DDL).

```ts
export const tags = pgTable('tag', { itemId: text('item_id').notNull(), tag: text().notNull() }, (t) => [
primaryKey({ name: 'tag_item_id_tag_pk', columns: [t.itemId, t.tag] }),
])
```

Repeated `drizzle-kit push --verbose` with zero schema changes emits on affected runs:

```sql
ALTER TABLE "tag" DROP CONSTRAINT "tag_item_id_tag_pk";
ALTER TABLE "tag" ADD CONSTRAINT "tag_item_id_tag_pk" PRIMARY KEY("item_id","tag");
```

We observed two databases with the identical schema disagree about whether the PK diffs, and the introspected column order flip between runs after DDL — declaring the "other" column order just moves which database churns.

### Root cause

`fromDatabase` builds composite-PK column order from this query, which has **no ORDER BY** — and `information_schema.constraint_column_usage` has no ordinal column at all, so row order is whatever the query plan happens to produce:

```sql
SELECT c.column_name, c.data_type, constraint_type, constraint_name, constraint_schema
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name)
JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema
AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
WHERE tc.table_name = '${tableName}' and constraint_schema = '${tableSchema}';
```

The differ then compares that arbitrary order against the declared `columns` array as an ordered list → spurious, plan-dependent diffs.

### Suggested fix

Join `information_schema.key_column_usage` (whose `ordinal_position` is the position within the key) and order by it. We run this as a local `pnpm patch` and push is now fully idempotent across both our databases:

```sql
LEFT JOIN (
SELECT constraint_schema AS kcu_schema, constraint_name AS kcu_name,
column_name AS kcu_column, ordinal_position AS kcu_position
FROM information_schema.key_column_usage
) kcu ON kcu.kcu_schema = tc.constraint_schema
AND kcu.kcu_name = tc.constraint_name
AND kcu.kcu_column = ccu.column_name
WHERE tc.table_name = '${tableName}' and constraint_schema = '${tableSchema}'
ORDER BY kcu.kcu_position NULLS LAST, c.column_name;
```

(The rename-subquery avoids `constraint_schema` becoming ambiguous with the earlier `USING` join. Alternatively, compare PK columns as sets, or sort both sides before comparing.)

Related: #3338 reports the same recreate-every-push symptom on MySQL; #3103 covered order-only diffs at generate time.

Contributor guide

Open the contributing guide

Research direction

Start in drizzle-kit/src/serializer/pgSerializer.ts and inspect the fromDatabase query that collects composite primary-key columns. Reproduce the issue with repeated drizzle-kit push --verbose runs against PostgreSQL, then verify that unchanged schemas no longer emit DROP/ADD primary-key statements and that column order remains stable across databases and runs.

Written by the indexing model from the issue text.

Assessment

Tech stack
postgresql, sql, typescript
Domain
databases
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.