also looking at this
fix(schema): emit inline FOREIGN KEY on SQLite CREATE TABLE (#1019)
#1020
6 files
+239
-10
| @@ -757,11 +757,27 @@ export function generateSql(plan: MigrationPlan): string[] { | ||
| 757 | 757 | createMigrationFile(combinedStatement, `create-${t.table}-table`) |
| 758 | 758 | } |
| 759 | 759 | |
| 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. | |
| 761 | 776 | for (const t of plan.tables) { |
| 762 | 777 | for (const c of t.columns) { |
| 763 | 778 | if (c.references) { |
| 764 | 779 | const alterTableStatement = driver.addForeignKey(t.table, c.name, c.references.table, c.references.column, c.references.onDelete, c.references.onUpdate) |
| 780 | if (!alterTableStatement) continue | |
| 765 | 781 | statements.push(alterTableStatement) |
| 766 | 782 | createMigrationFile(alterTableStatement, `alter-${t.table}-${c.name}`) |
| 767 | 783 | } |
| @@ -959,13 +975,17 @@ export function generateDiffSql(previous: MigrationPlan | undefined, next: Migra | ||
| 959 | 975 | } |
| 960 | 976 | } |
| 961 | 977 | |
| 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). | |
| 963 | 982 | for (const tableName of Object.keys(nextTables)) { |
| 964 | 983 | if (!prevTables[tableName]) { |
| 965 | 984 | const t = nextTables[tableName] |
| 966 | 985 | for (const c of t.columns) { |
| 967 | 986 | if (c.references) { |
| 968 | 987 | const alterTableStatement = driver.addForeignKey(t.table, c.name, c.references.table, c.references.column, c.references.onDelete, c.references.onUpdate) |
| 988 | if (!alterTableStatement) continue | |
| 969 | 989 | chunks.push(alterTableStatement) |
| 970 | 990 | createMigrationFile(alterTableStatement, `alter-${t.table}-${c.name}`) |
| 971 | 991 | } |