also looking at this
fix(sqlite): keep the foreign key when a diff adds a column
#1155
3 files
+209
-15
| @@ -147,6 +147,21 @@ export class SQLiteDriver implements DialectDriver { | ||
| 147 | 147 | return '' |
| 148 | 148 | } |
| 149 | 149 | |
| 150 | /** | |
| 151 | * `ALTER TABLE … ADD COLUMN`, with the column's FK inline. | |
| 152 | * | |
| 153 | * The FK used to be dropped here: `addForeignKey` returns '' on SQLite (no | |
| 154 | * `ADD CONSTRAINT`), and this rendered only the type and constraints, so a | |
| 155 | * `belongsTo` added to an EXISTING table produced a plain column and no | |
| 156 | * foreign key at all — silently, on a migration that reported success. A | |
| 157 | * fresh CREATE TABLE inlined it correctly the whole time. See #1154. | |
| 158 | * | |
| 159 | * SQLite accepts `ADD COLUMN … REFERENCES …` as long as the new column's | |
| 160 | * default is NULL. A non-NULL default is refused once foreign keys are on and | |
| 161 | * the table has rows (`Cannot add a REFERENCES column with non-NULL default | |
| 162 | * value`), so the diff engine sends that combination through `rebuildTable` | |
| 163 | * instead — see `addedColumnNeedsRebuild`. | |
| 164 | */ | |
| 150 | 165 | addColumn(tableName: string, column: ColumnPlan): string { |
| 151 | 166 | const typeSql = this.getColumnType(column) |
| 152 | 167 | const parts: string[] = [this.quoteIdentifier(column.name), typeSql] |
| @@ -160,6 +175,8 @@ export class SQLiteDriver implements DialectDriver { | ||
| 160 | 175 | parts.push(defaultValue) |
| 161 | 176 | } |
| 162 | 177 | |
| 178 | parts.push(...this.referencesClause(column)) | |
| 179 | ||
| 163 | 180 | return `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${parts.join(' ')};` |
| 164 | 181 | } |
| 165 | 182 | |
| @@ -335,22 +352,33 @@ export class SQLiteDriver implements DialectDriver { | ||
| 335 | 352 | parts.push(defaultValue) |
| 336 | 353 | } |
| 337 | 354 | |
| 338 | // Inline FK — for SQLite this is the ONLY path that works, since | |
| 339 | // SQLite doesn't support `ALTER TABLE ADD CONSTRAINT`. The | |
| 340 | // orchestrator (`generateSql` / `generateDiffSql` in migrations.ts) | |
| 341 | // skips its post-CREATE `addForeignKey` pass when emitting CREATE | |
| 342 | // TABLE so we don't duplicate the FK on dialects that accept both | |
| 343 | // forms. Enforcement still requires `PRAGMA foreign_keys = ON` on | |
| 344 | // the SQLite connection (off by default — set this in the | |
| 345 | // consumer's connection bootstrap). | |
| 346 | if (column.references) { | |
| 347 | parts.push(`REFERENCES ${this.quoteIdentifier(column.references.table)}(${this.quoteIdentifier(column.references.column)})`) | |
| 348 | if (column.references.onDelete) | |
| 349 | parts.push(`ON DELETE ${column.references.onDelete.toUpperCase()}`) | |
| 350 | if (column.references.onUpdate) | |
| 351 | parts.push(`ON UPDATE ${column.references.onUpdate.toUpperCase()}`) | |
| 352 | } | |
| 355 | parts.push(...this.referencesClause(column)) | |
| 353 | 356 | |
| 354 | 357 | return parts.join(' ') |
| 355 | 358 | } |
| 359 | ||
| 360 | /** | |
| 361 | * The inline `REFERENCES` clause, or nothing when the column declares no FK. | |
| 362 | * | |
| 363 | * For SQLite this is the ONLY path that works, since SQLite doesn't support | |
| 364 | * `ALTER TABLE ADD CONSTRAINT`. The orchestrator (`generateSql` / | |
| 365 | * `generateDiffSql` in migrations.ts) skips its post-CREATE `addForeignKey` | |
| 366 | * pass when emitting CREATE TABLE so we don't duplicate the FK on dialects | |
| 367 | * that accept both forms. Enforcement still requires `PRAGMA foreign_keys = | |
| 368 | * ON` on the SQLite connection (off by default — set this in the consumer's | |
| 369 | * connection bootstrap). | |
| 370 | * | |
| 371 | * Shared with `addColumn`: a column added to an existing table carries its FK | |
| 372 | * too, and only the CREATE TABLE path used to render it. See #1154. | |
| 373 | */ | |
| 374 | private referencesClause(column: ColumnPlan): string[] { | |
| 375 | if (!column.references) | |
| 376 | return [] | |
| 377 | const parts = [`REFERENCES ${this.quoteIdentifier(column.references.table)}(${this.quoteIdentifier(column.references.column)})`] | |
| 378 | if (column.references.onDelete) | |
| 379 | parts.push(`ON DELETE ${column.references.onDelete.toUpperCase()}`) | |
| 380 | if (column.references.onUpdate) | |
| 381 | parts.push(`ON UPDATE ${column.references.onUpdate.toUpperCase()}`) | |
| 382 | return parts | |
| 383 | } | |
| 356 | 384 | } |