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
packages/bun-query-builder/src/migrations.tsmodified+22-2
Changes to packages/bun-query-builder/src/migrations.ts
@@ -757,11 +757,27 @@ export function generateSql(plan: MigrationPlan): string[] {
757757 createMigrationFile(combinedStatement, `create-${t.table}-table`)
758758 }
759759
760 // First, create all foreign key constraints (ALTER statements - will execute last)
760 // Foreign key constraints (ALTER statements — execute last).
761 //
762 // MySQL/PostgreSQL strictly require the referenced table to exist
763 // before CREATE TABLE if the FK is declared inline, which makes the
764 // alphabetically-ordered `plan.tables` iteration order fragile.
765 // Deferring the FK to a separate ALTER pass means tables can be
766 // created in any order and FKs land after.
767 //
768 // SQLite skips this pass entirely — it doesn't support `ALTER TABLE
769 // ADD CONSTRAINT FOREIGN KEY` at all. Its FKs ride inline on the
770 // CREATE TABLE statement via `renderColumn`, which works because
771 // SQLite is lenient about forward-reference targets (the constraint
772 // is checked at INSERT time, not CREATE). SQLite's `addForeignKey`
773 // returns an empty string so the `if (!alterTableStatement)
774 // continue` line below skips the no-op file. See
775 // stacksjs/bun-query-builder#1019.
761776 for (const t of plan.tables) {
762777 for (const c of t.columns) {
763778 if (c.references) {
764779 const alterTableStatement = driver.addForeignKey(t.table, c.name, c.references.table, c.references.column, c.references.onDelete, c.references.onUpdate)
780 if (!alterTableStatement) continue
765781 statements.push(alterTableStatement)
766782 createMigrationFile(alterTableStatement, `alter-${t.table}-${c.name}`)
767783 }
@@ -959,13 +975,17 @@ export function generateDiffSql(previous: MigrationPlan | undefined, next: Migra
959975 }
960976 }
961977
962 // 3) Add foreign key constraints for new tables (ALTER statements - execute last)
978 // 3) Add foreign key constraints for new tables (ALTER statements -
979 // execute last). Mirrors the initial-migration FK pass above:
980 // MySQL/Postgres use deferred ALTER, SQLite skips (FKs are already
981 // inline from CREATE TABLE).
963982 for (const tableName of Object.keys(nextTables)) {
964983 if (!prevTables[tableName]) {
965984 const t = nextTables[tableName]
966985 for (const c of t.columns) {
967986 if (c.references) {
968987 const alterTableStatement = driver.addForeignKey(t.table, c.name, c.references.table, c.references.column, c.references.onDelete, c.references.onUpdate)
988 if (!alterTableStatement) continue
969989 chunks.push(alterTableStatement)
970990 createMigrationFile(alterTableStatement, `alter-${t.table}-${c.name}`)
971991 }