drizzle-team / drizzle-team/drizzle-orm

[BUG]: SQLite generate orders DROP TABLE alphabetically, so a parent can be dropped before the child that references it

Open
#6,251 0 comments 0 reactions 0 assignees View on GitHub
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.4

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

1.0.0-rc.4

### Other packages

better-sqlite3@13.0.3, node:sqlite (Node 24.17.0)

### Describe the Bug

When a `generate` removes several tables at once, the `DROP TABLE` statements come out in alphabetical order, ignoring both declaration order and the foreign keys between them. SQLite runs an implicit `DELETE FROM` before removing a table, so dropping a parent while a child still references it fires the constraint and the migration rolls back.

Two schemas that differ only in the names. Both declare the child first, and in both the child references the parent:

```ts
// A
export const zzzChild = sqliteTable('zzz_child', { id: integer('id').primaryKey(), p: integer('p').references(() => aaaParent.id) });
export const aaaParent = sqliteTable('aaa_parent', { id: integer('id').primaryKey(), code: text('code') });
```

```sql
DROP TABLE `aaa_parent`;--> statement-breakpoint
DROP TABLE `zzz_child`;
```

```ts
// B
export const aaaChild = sqliteTable('aaa_child', { id: integer('id').primaryKey(), p: integer('p').references(() => zzzParent.id) });
export const zzzParent = sqliteTable('zzz_parent', { id: integer('id').primaryKey(), code: text('code') });
```

```sql
DROP TABLE `aaa_child`;--> statement-breakpoint
DROP TABLE `zzz_parent`;
```

The order followed the names, so whether the migration applies is decided by how the table names happen to sort.

A, against a database with rows in both:

```
FOREIGN KEY constraint failed
```

B applies. A also applies when the tables are empty, which is what makes this pass in a fresh database and fail against production.

Nothing guards this path. `PRAGMA foreign_keys=OFF` is only emitted around the `__new_` table rebuild, not around plain drops, and the D1 migrator doesn't disable them either, it collects the statements into `db.session.batch()`. `node:sqlite` and `better-sqlite3` both default `PRAGMA foreign_keys` to 1, so it isn't an unusual setup that hits this.

#5388 does this for views, adding a `viewDeps.ts` that sorts create and drop by dependency, for the same reason: "dropping a base view before its dependents can also fail". Tables want the same sort on the drop side. #4458 asks for the general version across FKs and indexes, and the rebuild issues (#1813, #4089, #5469, #5782) are a different code path, since none of those is a plain drop.

Same on 1.0.0-rc.5-ab785fc and 0.31.10.

Contributor guide

Open the contributing guide

Research direction

Start at the SQLite generate path and reproduce the failure with the two schemas in this report against populated tables. Compare the table drop ordering with the dependency-based approach described in viewDeps.ts. Done means generated migrations drop dependent tables before their parents and apply successfully with foreign-key enforcement enabled.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, sqlite, typescript
Domain
database, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 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.