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
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