ReviewOS

also looking at this

stacks/bun-query-builder

fix(schema): emit inline FOREIGN KEY on SQLite CREATE TABLE (#1019)

#1020
Merged glennmichael123 wants to merge fix/inline-foreign-keys into main
6 files +239 -10
10 days ago

Summary

  • SQLite `renderColumn` now emits inline `REFERENCES "table"("col") [ON DELETE …] [ON UPDATE …]` from `column.references` metadata.
  • SQLite `addForeignKey` returns an empty string; orchestrator skips unrunnable `ALTER TABLE ADD CONSTRAINT` files for SQLite (downstream "strip ADD CONSTRAINT files" workarounds can be retired).
  • MySQL / PostgreSQL keep the existing deferred-ALTER strategy — inline FKs there would fail when iteration order references a forward-defined table, and emitting both inline + ALTER would create duplicate constraints.

Closes

#1019

Test plan

  • `bun test` — 1178/1178 pass.
  • New `test/foreign-keys-inline.test.ts` (6 cases) — sqlite inline emission; mysql/postgres NO inline + deferred ALTER; onUpdate/onDelete propagation; no stray REFERENCES on non-FK columns.
  • `test/migrations.diff.test.ts` — added sqlite-specific case asserting inline FK rides on the CREATE TABLE for a newly-added table in a diff.
  • Existing FK assertions in `migrations.snapshot.test.ts` and `migrations.attributes.test.ts` (which run against PostgreSQL) still pass — they continue to see `FOREIGN KEY` / `REFERENCES` from the deferred-ALTER pass.
  • Postgres end-to-end seeder tests (`test/seeder.e2e.test.ts`) still pass — the deferred-ALTER strategy preserves the table-creation-order independence those tests depend on.

Repro before the fix

Defining a model with `belongsTo` and generating migrations on SQLite produced:

```sql CREATE TABLE IF NOT EXISTS "judge_reviews" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "judge_id" INTEGER, "user_id" INTEGER, ... ); ```

No FK at all. (`addForeignKey` emitted a separate `ALTER TABLE ADD CONSTRAINT` file that SQLite rejects; downstream consumers stripped that file before running migrations, leaving zero referential integrity.)

After the fix, the same model produces:

```sql CREATE TABLE IF NOT EXISTS "judge_reviews" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "judge_id" INTEGER not null REFERENCES "judges"("id") ON DELETE CASCADE, "user_id" INTEGER not null REFERENCES "users"("id") ON DELETE CASCADE, ... ); ```

…and the standalone ALTER file is no longer emitted (`addForeignKey` returns `''`, orchestrator skips).

Note for SQLite users: FK enforcement requires `PRAGMA foreign_keys = ON` on the connection — bqb declares the constraint, but SQLite doesn't enforce it unless that pragma is set.

🤖 Generated with Claude Code

6 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.