@happyvertical/smrt-core 0.51.11 → 0.51.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/agents/schema-paths.md +19 -6
  2. package/dist/class.d.ts +1 -1
  3. package/dist/class.d.ts.map +1 -1
  4. package/dist/class.js +4 -2
  5. package/dist/class.js.map +1 -1
  6. package/dist/decorators/index.d.ts +19 -0
  7. package/dist/decorators/index.d.ts.map +1 -1
  8. package/dist/decorators/index.js.map +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/manifest/static-manifest.js +1 -1
  11. package/dist/manifest/static-manifest.js.map +1 -1
  12. package/dist/manifest/store.js +1 -1
  13. package/dist/manifest.json +1 -1
  14. package/dist/migrations/differ.d.ts +64 -5
  15. package/dist/migrations/differ.d.ts.map +1 -1
  16. package/dist/migrations/differ.js +270 -37
  17. package/dist/migrations/differ.js.map +1 -1
  18. package/dist/migrations/index.d.ts +1 -1
  19. package/dist/migrations/index.d.ts.map +1 -1
  20. package/dist/migrations/index.js +2 -2
  21. package/dist/migrations/sqlite-rebuild.d.ts +41 -0
  22. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -1
  23. package/dist/migrations/sqlite-rebuild.js +68 -13
  24. package/dist/migrations/sqlite-rebuild.js.map +1 -1
  25. package/dist/migrations.js +2 -2
  26. package/dist/registry/db-cache-key.d.ts +47 -0
  27. package/dist/registry/db-cache-key.d.ts.map +1 -0
  28. package/dist/registry/db-cache-key.js +201 -0
  29. package/dist/registry/db-cache-key.js.map +1 -0
  30. package/dist/registry/schema-builder.d.ts.map +1 -1
  31. package/dist/registry/schema-builder.js +6 -1
  32. package/dist/registry/schema-builder.js.map +1 -1
  33. package/dist/registry.d.ts +3 -0
  34. package/dist/registry.d.ts.map +1 -1
  35. package/dist/registry.js +5 -6
  36. package/dist/registry.js.map +1 -1
  37. package/dist/scanner/types.d.ts +2 -0
  38. package/dist/scanner/types.d.ts.map +1 -1
  39. package/dist/scanner/types.js.map +1 -1
  40. package/dist/schema/backfill.d.ts +16 -0
  41. package/dist/schema/backfill.d.ts.map +1 -0
  42. package/dist/schema/backfill.js +26 -0
  43. package/dist/schema/backfill.js.map +1 -0
  44. package/dist/schema/generator.d.ts.map +1 -1
  45. package/dist/schema/generator.js +3 -0
  46. package/dist/schema/generator.js.map +1 -1
  47. package/dist/schema/types.d.ts +12 -0
  48. package/dist/schema/types.d.ts.map +1 -1
  49. package/dist/smrt-knowledge.json +5 -5
  50. package/package.json +10 -10
@@ -1,5 +1,10 @@
1
- import { SchemaChange, SchemaDefinition, SchemaDiff, SQLDataType } from '../schema/types.js';
1
+ import { ColumnDefinition, SchemaChange, SchemaDefinition, SchemaDiff, SQLDataType } from '../schema/types.js';
2
2
  import { DatabaseInterface } from './types.js';
3
+ /**
4
+ * `mismatch.actual` of the manual change reported when a required column
5
+ * could not be added to a populated table (#3008): the column is absent.
6
+ */
7
+ export declare const REQUIRED_COLUMN_NOT_ADDED = "(column not added)";
3
8
  /**
4
9
  * Options for schema comparison
5
10
  */
@@ -153,6 +158,13 @@ export declare class SchemaComparer {
153
158
  * re-reading `getTableSchema` per relationship would multiply catalog
154
159
  * round-trips on a wide schema.
155
160
  */
161
+ /**
162
+ * #3008: indexes planned earlier in this table's batch (DuckDB unique
163
+ * follow-ups), which a later `SET NOT NULL` must also drop and recreate.
164
+ */
165
+ private batchIndexes;
166
+ /** #3008: per-table single-column unique targets, set by compareTable. */
167
+ private uniqueTargets;
156
168
  private liveSchemas;
157
169
  /** Convergence plan for this run; `null` on non-PostgreSQL engines. */
158
170
  private uuidConvergence;
@@ -654,11 +666,15 @@ export declare class SchemaComparer {
654
666
  * - `NOT NULL` with a default: inline on SQLite/PostgreSQL (existing rows
655
667
  * receive the default); DuckDB adds with `DEFAULT` then a separate
656
668
  * `ALTER COLUMN ... SET NOT NULL`.
657
- * - `NOT NULL` without a default: enforced only when the table is empty
669
+ * - `NOT NULL` without a default: enforced inline when the table is empty
658
670
  * (inline on SQLite/PostgreSQL, `SET NOT NULL` step on DuckDB). On a
659
- * populated table there is nothing to backfill with, so the column is
660
- * added nullable and a manual `alter_column` (`set_not_null`) is reported
661
- * with the exact remediation instead of emitting DDL every engine rejects.
671
+ * populated table it needs a declared per-row `backfill` (#3008): add
672
+ * nullable, `UPDATE` from the backfill, then `SET NOT NULL` (SQLite: a
673
+ * table rebuild). Without one — or when the backfill yields NULL or does
674
+ * not evaluate — the column is NOT added and a manual `alter_column`
675
+ * (`mismatch.actual === REQUIRED_COLUMN_NOT_ADDED`) names it.
676
+ * - A backfill also wins over a declared default for existing rows; the
677
+ * default is then set after the fill, for future inserts only.
662
678
  * - `UNIQUE`: inline on PostgreSQL (a real constraint); SQLite/DuckDB get a
663
679
  * separate `CREATE UNIQUE INDEX <table>_<col>_key` (the PostgreSQL
664
680
  * constraint-index name, so the orphan sweep leaves it alone).
@@ -670,6 +686,49 @@ export declare class SchemaComparer {
670
686
  * orchestrator execute it as one unit.
671
687
  */
672
688
  private generateAddColumnChanges;
689
+ /**
690
+ * #3008: fill a column's NULLs from its declared per-row backfill, then
691
+ * enforce NOT NULL (PostgreSQL/DuckDB; SQLite goes through a table
692
+ * rebuild). {@link probeBackfill} has already checked, at plan time, that
693
+ * the expression yields a value for every row; should a row written after
694
+ * that still produce NULL, `SET NOT NULL` fails and the whole migration
695
+ * unit rolls back.
696
+ */
697
+ private backfillStatements;
698
+ /**
699
+ * #3008: evaluate a declared backfill against the live rows before any DDL
700
+ * is planned. Returns a reason the column cannot be backfilled — the
701
+ * expression yields NULL for some row, or does not evaluate at all — or
702
+ * `undefined` when every row that needs a value gets one. `existing` is
703
+ * true when the column already exists (the tighten path), so only its NULL
704
+ * rows count.
705
+ *
706
+ * Plan-time, so the refusal names the table and column in the report
707
+ * instead of surfacing later as a bare, redacted engine error.
708
+ */
709
+ private isUniqueTarget;
710
+ private probeBackfill;
711
+ /**
712
+ * #3008: the column definition a SQLite backfill rebuild appends — NOT
713
+ * NULL, plus the declared default (for future inserts) and CHECK.
714
+ */
715
+ private sqliteAddedColumnDefinition;
716
+ /**
717
+ * `ALTER COLUMN ... SET NOT NULL`, made executable on DuckDB. DuckDB
718
+ * refuses to alter a table any index depends on ("Cannot alter entry …
719
+ * because there are entries that depend on it"), so there the table's
720
+ * explicit indexes are dropped around the ALTER and recreated from their
721
+ * own DDL, inside the same migration unit.
722
+ */
723
+ private setNotNullStatements;
724
+ /**
725
+ * #3008: the manual change reported instead of an `add_column` when a
726
+ * required column without a default or backfill cannot be added to a
727
+ * populated table (or when a SQLite backfill rebuild is refused). The
728
+ * column is NOT added; `compareTable` also withholds the indexes and
729
+ * foreign keys that would target it.
730
+ */
731
+ requiredColumnNotAddedChange(tableName: string, colName: string, colDef: ColumnDefinition, reason?: string): SchemaChange;
673
732
  /**
674
733
  * Generate SQL for dropping a column
675
734
  */
@@ -1 +1 @@
1
- {"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/migrations/differ.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"differ.d.ts","sourceRoot":"","sources":["../../src/migrations/differ.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ import { columnsAllValuesUuidShapedBatch, columnsHaveNonEmptyValueBatch, nonEmpt
7
7
  import { maskSampleValue, probeCastSafety, renderJsonbColumnConversion, renderTimestamptzColumnConversion } from "../schema/text-cast-probe.js";
8
8
  import { describeForeignKeyTypeConflict, planUuidConvergence, renderUuidConvergenceRepairHint, renderUuidShapeProbe } from "../schema/uuid-convergence.js";
9
9
  import "../schema/utils.js";
10
- import { planSqliteTableRebuilds, sqliteRebuildPlaceholderSql } from "./sqlite-rebuild.js";
10
+ import { planSqliteTableRebuilds, sqliteBackfillPlaceholderSql, sqliteRebuildPlaceholderSql } from "./sqlite-rebuild.js";
11
11
  import { createLogger } from "@happyvertical/logger";
12
12
  //#region src/migrations/differ.ts
13
13
  /**
@@ -18,6 +18,11 @@ import { createLogger } from "@happyvertical/logger";
18
18
  */
19
19
  var logger = createLogger({ level: "info" });
20
20
  /**
21
+ * `mismatch.actual` of the manual change reported when a required column
22
+ * could not be added to a populated table (#3008): the column is absent.
23
+ */
24
+ var REQUIRED_COLUMN_NOT_ADDED = "(column not added)";
25
+ /**
21
26
  * Valid SQLDataType values for validation
22
27
  */
23
28
  var VALID_SQL_DATA_TYPES = /* @__PURE__ */ new Set([
@@ -313,6 +318,13 @@ var SchemaComparer = class SchemaComparer {
313
318
  * re-reading `getTableSchema` per relationship would multiply catalog
314
319
  * round-trips on a wide schema.
315
320
  */
321
+ /**
322
+ * #3008: indexes planned earlier in this table's batch (DuckDB unique
323
+ * follow-ups), which a later `SET NOT NULL` must also drop and recreate.
324
+ */
325
+ batchIndexes = /* @__PURE__ */ new Map();
326
+ /** #3008: per-table single-column unique targets, set by compareTable. */
327
+ uniqueTargets = /* @__PURE__ */ new Map();
316
328
  liveSchemas = /* @__PURE__ */ new Map();
317
329
  /** Convergence plan for this run; `null` on non-PostgreSQL engines. */
318
330
  uuidConvergence = null;
@@ -413,19 +425,66 @@ var SchemaComparer = class SchemaComparer {
413
425
  */
414
426
  async compareTable(tableName, manifest, manifestSchemas = {}) {
415
427
  const changes = [];
428
+ this.batchIndexes.delete(tableName);
429
+ this.uniqueTargets.set(tableName, new Set((manifest.indexes ?? []).filter((index) => index.unique && !index.where && index.columns.length === 1).map((index) => index.columns[0])));
416
430
  const dbSchema = await this.getLiveSchema(tableName);
417
431
  if (!dbSchema) return changes;
418
432
  const columnChanges = await this.compareColumns(tableName, manifest, dbSchema);
419
- changes.push(...this.engine === "sqlite" ? await planSqliteTableRebuilds({
433
+ const plannedColumnChanges = this.engine === "sqlite" ? await planSqliteTableRebuilds({
420
434
  db: this.db,
421
435
  tableName,
422
436
  changes: columnChanges,
423
- mapType: (type) => this.ddlStrategy.mapType(type)
424
- }) : columnChanges);
437
+ mapType: (type) => this.ddlStrategy.mapType(type),
438
+ addedColumnDefinition: (change) => this.sqliteAddedColumnDefinition(change),
439
+ addedColumnFollowUps: (change) => change.column?.unique && change.name ? [`CREATE UNIQUE INDEX ${this.quoteIdentifier(uniqueColumnIndexName(tableName, change.name))} ON ${this.quoteIdentifier(tableName)} (${this.quoteIdentifier(change.name)})`] : []
440
+ }) : columnChanges;
441
+ const notAdded = /* @__PURE__ */ new Set();
442
+ for (const change of plannedColumnChanges) {
443
+ if (change.type === "add_column" && change.name && change.column && !change.sqlStatements && change.sql?.trim().startsWith("--")) {
444
+ const reason = change.sql.replace(/^--\s*/, "");
445
+ changes.push(this.requiredColumnNotAddedChange(tableName, change.name, change.column, reason));
446
+ notAdded.add(change.name);
447
+ continue;
448
+ }
449
+ if (change.type === "alter_column" && change.name && change.mismatch?.actual === "(column not added)") notAdded.add(change.name);
450
+ changes.push(change);
451
+ }
425
452
  const dbIndexPredicates = await this.getDbIndexPredicates(tableName);
426
453
  const indexChanges = this.compareIndexes(tableName, manifest, dbSchema, dbIndexPredicates);
427
- changes.push(...indexChanges);
428
- changes.push(...await this.compareForeignKeys(tableName, manifest, dbSchema, manifestSchemas));
454
+ const foreignKeyChanges = await this.compareForeignKeys(tableName, manifest, dbSchema, manifestSchemas);
455
+ if (notAdded.size === 0) {
456
+ changes.push(...indexChanges, ...foreignKeyChanges);
457
+ return changes;
458
+ }
459
+ const withheld = /* @__PURE__ */ new Map();
460
+ const dependsOnMissing = (change) => {
461
+ if (change.type === "add_index") return change.index?.columns.find((column) => notAdded.has(column));
462
+ if (change.type === "add_foreign_key") {
463
+ const column = change.foreignKey?.column;
464
+ return column && notAdded.has(column) ? column : void 0;
465
+ }
466
+ };
467
+ const withheldIndexNames = new Set(indexChanges.filter((change) => dependsOnMissing(change)).map((change) => change.index?.name ?? change.name));
468
+ for (const change of [...indexChanges, ...foreignKeyChanges]) {
469
+ if (change.type === "drop_index" && withheldIndexNames.has(change.index?.name ?? change.name)) continue;
470
+ const missing = dependsOnMissing(change);
471
+ if (!missing) {
472
+ changes.push(change);
473
+ continue;
474
+ }
475
+ const label = change.type === "add_index" ? `index ${change.index?.name ?? change.name ?? "(unnamed)"}` : `foreign key on ${missing}`;
476
+ withheld.set(missing, [...withheld.get(missing) ?? [], label]);
477
+ }
478
+ for (const change of changes) {
479
+ const labels = change.name && change.mismatch?.actual === "(column not added)" ? withheld.get(change.name) : void 0;
480
+ if (!labels || !change.advisory) continue;
481
+ const note = ` Withheld until it exists: ${labels.join(", ")}.`;
482
+ change.advisory = {
483
+ ...change.advisory,
484
+ message: `${change.advisory.message}${note}`
485
+ };
486
+ change.sql = `${change.sql}${note}`;
487
+ }
429
488
  return changes;
430
489
  }
431
490
  /**
@@ -1500,7 +1559,38 @@ var SchemaComparer = class SchemaComparer {
1500
1559
  const quotedTable = this.quoteIdentifier(tableName);
1501
1560
  const quotedCol = this.quoteIdentifier(colName);
1502
1561
  const setNotNull = `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET NOT NULL`;
1503
- if (!this.supportsAlterColumn()) changes.push(this.buildAlterColumnChange({
1562
+ const backfillNeeded = Boolean(colDef.backfill) && await this.columnHasNulls(tableName, colName);
1563
+ const unusableBackfill = backfillNeeded && colDef.backfill ? await this.probeBackfill(tableName, colName, colDef.backfill, true, this.isUniqueTarget(tableName, colName, colDef)) : void 0;
1564
+ if (backfillNeeded && unusableBackfill) changes.push({
1565
+ type: "alter_column",
1566
+ table: tableName,
1567
+ name: colName,
1568
+ column: colDef,
1569
+ alteration: "set_not_null",
1570
+ mismatch: {
1571
+ expected: "NOT NULL",
1572
+ actual: "NULL"
1573
+ },
1574
+ sql: `-- ${tableName}.${colName} is required but live rows hold NULL and ${unusableBackfill}`,
1575
+ advisory: {
1576
+ severity: "warning",
1577
+ message: `${tableName}.${colName} is required but live rows hold NULL and ${unusableBackfill}.`,
1578
+ suggestedSql: [`UPDATE ${quotedTable} SET ${quotedCol} = <value> WHERE ${quotedCol} IS NULL`, this.supportsAlterColumn() ? setNotNull : "-- SQLite: enforcing NOT NULL afterwards requires a table rebuild (#2370)"]
1579
+ }
1580
+ });
1581
+ else if (this.engine === "sqlite" && backfillNeeded) changes.push({
1582
+ type: "alter_column",
1583
+ table: tableName,
1584
+ name: colName,
1585
+ column: colDef,
1586
+ alteration: "set_not_null",
1587
+ mismatch: {
1588
+ expected: "NOT NULL",
1589
+ actual: "NULL"
1590
+ },
1591
+ sql: sqliteBackfillPlaceholderSql(colName)
1592
+ });
1593
+ else if (!this.supportsAlterColumn()) changes.push(this.buildAlterColumnChange({
1504
1594
  tableName,
1505
1595
  colName,
1506
1596
  colDef,
@@ -1509,6 +1599,15 @@ var SchemaComparer = class SchemaComparer {
1509
1599
  actual: "NULL",
1510
1600
  statements: null
1511
1601
  }));
1602
+ else if (backfillNeeded && colDef.backfill) changes.push(this.buildAlterColumnChange({
1603
+ tableName,
1604
+ colName,
1605
+ colDef,
1606
+ alteration: "set_not_null",
1607
+ expected: "NOT NULL",
1608
+ actual: "NULL",
1609
+ statements: await this.backfillStatements(tableName, colName, colDef.backfill)
1610
+ }));
1512
1611
  else if (hasManifestDefault && manifestDefaultSql !== void 0) changes.push(this.buildAlterColumnChange({
1513
1612
  tableName,
1514
1613
  colName,
@@ -1516,7 +1615,7 @@ var SchemaComparer = class SchemaComparer {
1516
1615
  alteration: "set_not_null",
1517
1616
  expected: "NOT NULL",
1518
1617
  actual: "NULL",
1519
- statements: [`UPDATE ${quotedTable} SET ${quotedCol} = ${manifestDefaultSql} WHERE ${quotedCol} IS NULL`, setNotNull]
1618
+ statements: [`UPDATE ${quotedTable} SET ${quotedCol} = ${manifestDefaultSql} WHERE ${quotedCol} IS NULL`, ...await this.setNotNullStatements(tableName, colName)]
1520
1619
  }));
1521
1620
  else if (await this.columnHasNulls(tableName, colName)) changes.push({
1522
1621
  type: "alter_column",
@@ -1542,7 +1641,7 @@ var SchemaComparer = class SchemaComparer {
1542
1641
  alteration: "set_not_null",
1543
1642
  expected: "NOT NULL",
1544
1643
  actual: "NULL",
1545
- statements: [setNotNull]
1644
+ statements: await this.setNotNullStatements(tableName, colName)
1546
1645
  }));
1547
1646
  } else if (!manifestNotNull && dbNotNull) changes.push(this.buildAlterColumnChange({
1548
1647
  tableName,
@@ -2101,11 +2200,15 @@ var SchemaComparer = class SchemaComparer {
2101
2200
  * - `NOT NULL` with a default: inline on SQLite/PostgreSQL (existing rows
2102
2201
  * receive the default); DuckDB adds with `DEFAULT` then a separate
2103
2202
  * `ALTER COLUMN ... SET NOT NULL`.
2104
- * - `NOT NULL` without a default: enforced only when the table is empty
2203
+ * - `NOT NULL` without a default: enforced inline when the table is empty
2105
2204
  * (inline on SQLite/PostgreSQL, `SET NOT NULL` step on DuckDB). On a
2106
- * populated table there is nothing to backfill with, so the column is
2107
- * added nullable and a manual `alter_column` (`set_not_null`) is reported
2108
- * with the exact remediation instead of emitting DDL every engine rejects.
2205
+ * populated table it needs a declared per-row `backfill` (#3008): add
2206
+ * nullable, `UPDATE` from the backfill, then `SET NOT NULL` (SQLite: a
2207
+ * table rebuild). Without one — or when the backfill yields NULL or does
2208
+ * not evaluate — the column is NOT added and a manual `alter_column`
2209
+ * (`mismatch.actual === REQUIRED_COLUMN_NOT_ADDED`) names it.
2210
+ * - A backfill also wins over a declared default for existing rows; the
2211
+ * default is then set after the fill, for future inserts only.
2109
2212
  * - `UNIQUE`: inline on PostgreSQL (a real constraint); SQLite/DuckDB get a
2110
2213
  * separate `CREATE UNIQUE INDEX <table>_<col>_key` (the PostgreSQL
2111
2214
  * constraint-index name, so the orphan sweep leaves it alone).
@@ -2127,35 +2230,44 @@ var SchemaComparer = class SchemaComparer {
2127
2230
  const hasDefault = colDef.defaultValue !== void 0 && colDef.defaultValue !== null;
2128
2231
  const formattedDefault = hasDefault ? this.ddlStrategy.formatDefaultValue(colDef.defaultValue, validatedType) : void 0;
2129
2232
  const inlineConstraintsAllowed = this.engine !== "duckdb" && this.engine !== "json";
2130
- const setNotNull = `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET NOT NULL`;
2233
+ `${quotedTable}${quotedCol}`;
2131
2234
  let enforceNotNull = false;
2132
- if (colDef.notNull) if (hasDefault) enforceNotNull = true;
2133
- else if (!await this.tableHasRows(tableName)) enforceNotNull = true;
2134
- else extraChanges.push({
2135
- type: "alter_column",
2136
- table: tableName,
2137
- name: colName,
2138
- column: colDef,
2139
- alteration: "set_not_null",
2140
- mismatch: {
2141
- expected: "NOT NULL",
2142
- actual: "NULL"
2143
- },
2144
- sql: `-- ${tableName}.${colName} is required by the manifest but has no default to backfill existing rows; the column is added nullable. Backfill it, then run: ${setNotNull}`,
2145
- advisory: {
2146
- severity: "warning",
2147
- message: `${tableName}.${colName} is required by the manifest but has no default, and ${tableName} already holds rows — no engine can add it NOT NULL. It is added nullable; backfill it, then rerun db:migrate (or declare a default on the field).`,
2148
- suggestedSql: [`UPDATE ${quotedTable} SET ${quotedCol} = <value> WHERE ${quotedCol} IS NULL`, this.supportsAlterColumn() ? setNotNull : `-- SQLite: enforcing NOT NULL afterwards requires a table rebuild (#2370)`]
2149
- }
2150
- });
2235
+ const backfillFollowUps = [];
2236
+ let deferDefault = false;
2237
+ if (colDef.notNull) {
2238
+ const populated = colDef.backfill || !hasDefault ? await this.tableHasRows(tableName) : true;
2239
+ if (colDef.backfill && populated) {
2240
+ const unusable = await this.probeBackfill(tableName, colName, colDef.backfill, false, this.isUniqueTarget(tableName, colName, colDef));
2241
+ if (unusable) return [this.requiredColumnNotAddedChange(tableName, colName, colDef, unusable)];
2242
+ if (this.engine === "sqlite") return [{
2243
+ type: "add_column",
2244
+ table: tableName,
2245
+ name: colName,
2246
+ column: colDef,
2247
+ sql: sqliteBackfillPlaceholderSql(colName)
2248
+ }];
2249
+ deferDefault = formattedDefault !== void 0;
2250
+ backfillFollowUps.push(...await this.backfillStatements(tableName, colName, colDef.backfill, deferDefault ? [this.generateSetDefaultSQL(tableName, colName, formattedDefault, validatedType)] : []));
2251
+ } else if (hasDefault || !populated) enforceNotNull = true;
2252
+ else return [this.requiredColumnNotAddedChange(tableName, colName, colDef)];
2253
+ }
2151
2254
  if (enforceNotNull && inlineConstraintsAllowed) parts.push("NOT NULL");
2152
2255
  if (colDef.unique && this.engine === "postgres") parts.push("UNIQUE");
2153
- if (formattedDefault !== void 0) parts.push(`DEFAULT ${formattedDefault}`);
2256
+ if (formattedDefault !== void 0 && !deferDefault) parts.push(`DEFAULT ${formattedDefault}`);
2154
2257
  if (colDef.check) if (inlineConstraintsAllowed) parts.push(`CHECK (${colDef.check})`);
2155
2258
  else logger.warn(`[SchemaComparer] ${this.engine} rejects CHECK constraints in ADD COLUMN; ${tableName}.${colName} is added without CHECK (${colDef.check})`);
2156
2259
  const addColumn = `ALTER TABLE ${quotedTable} ADD COLUMN ${parts.join(" ")}`;
2157
- if (enforceNotNull && !inlineConstraintsAllowed) followUps.push(setNotNull);
2158
- if (colDef.unique && this.engine !== "postgres") followUps.push(`CREATE UNIQUE INDEX ${this.quoteIdentifier(uniqueColumnIndexName(tableName, colName))} ON ${quotedTable} (${quotedCol})`);
2260
+ if (enforceNotNull && !inlineConstraintsAllowed) followUps.push(...await this.setNotNullStatements(tableName, colName));
2261
+ followUps.push(...backfillFollowUps);
2262
+ if (colDef.unique && this.engine !== "postgres") {
2263
+ const indexName = uniqueColumnIndexName(tableName, colName);
2264
+ const createIndex = `CREATE UNIQUE INDEX ${this.quoteIdentifier(indexName)} ON ${quotedTable} (${quotedCol})`;
2265
+ followUps.push(createIndex);
2266
+ this.batchIndexes.set(tableName, [...this.batchIndexes.get(tableName) ?? [], {
2267
+ name: indexName,
2268
+ sql: createIndex
2269
+ }]);
2270
+ }
2159
2271
  const statements = [addColumn, ...followUps];
2160
2272
  return [{
2161
2273
  type: "add_column",
@@ -2167,6 +2279,127 @@ var SchemaComparer = class SchemaComparer {
2167
2279
  }, ...extraChanges];
2168
2280
  }
2169
2281
  /**
2282
+ * #3008: fill a column's NULLs from its declared per-row backfill, then
2283
+ * enforce NOT NULL (PostgreSQL/DuckDB; SQLite goes through a table
2284
+ * rebuild). {@link probeBackfill} has already checked, at plan time, that
2285
+ * the expression yields a value for every row; should a row written after
2286
+ * that still produce NULL, `SET NOT NULL` fails and the whole migration
2287
+ * unit rolls back.
2288
+ */
2289
+ async backfillStatements(tableName, colName, backfill, alterBeforeNotNull = []) {
2290
+ const quotedTable = this.quoteIdentifier(tableName);
2291
+ const quotedCol = this.quoteIdentifier(colName);
2292
+ const statements = [`UPDATE ${quotedTable} SET ${quotedCol} = (${backfill}) WHERE ${quotedCol} IS NULL`];
2293
+ statements.push(...await this.setNotNullStatements(tableName, colName, alterBeforeNotNull));
2294
+ return statements;
2295
+ }
2296
+ /**
2297
+ * #3008: evaluate a declared backfill against the live rows before any DDL
2298
+ * is planned. Returns a reason the column cannot be backfilled — the
2299
+ * expression yields NULL for some row, or does not evaluate at all — or
2300
+ * `undefined` when every row that needs a value gets one. `existing` is
2301
+ * true when the column already exists (the tighten path), so only its NULL
2302
+ * rows count.
2303
+ *
2304
+ * Plan-time, so the refusal names the table and column in the report
2305
+ * instead of surfacing later as a bare, redacted engine error.
2306
+ */
2307
+ isUniqueTarget(tableName, colName, colDef) {
2308
+ return Boolean(colDef.unique) || (this.uniqueTargets.get(tableName)?.has(colName) ?? false);
2309
+ }
2310
+ async probeBackfill(tableName, colName, backfill, existing, unique = false) {
2311
+ const quotedCol = this.quoteIdentifier(colName);
2312
+ const where = existing ? `${quotedCol} IS NULL AND (${backfill}) IS NULL` : `(${backfill}) IS NULL`;
2313
+ try {
2314
+ if (((await this.db.query(`SELECT 1 AS present FROM ${this.quoteIdentifier(tableName)} WHERE ${where} LIMIT 1`)).rows?.length ?? 0) > 0) return `its declared backfill (${backfill}) yields NULL for at least one existing row, so NOT NULL cannot be enforced; make the expression non-null for every row (e.g. wrap it in COALESCE)`;
2315
+ if (unique) {
2316
+ const value = existing ? `COALESCE(${quotedCol}, (${backfill}))` : `(${backfill})`;
2317
+ if (((await this.db.query(`SELECT ${value} AS v FROM ${this.quoteIdentifier(tableName)} GROUP BY ${value} HAVING COUNT(*) > 1 LIMIT 1`)).rows?.length ?? 0) > 0) return `its declared backfill (${backfill}) gives two or more existing rows the same value, but the column is unique; derive a per-row value (e.g. from id)`;
2318
+ }
2319
+ return;
2320
+ } catch (err) {
2321
+ logger.debug(`[SchemaComparer] Backfill probe failed for ${tableName}.${colName}`, { error: err instanceof Error ? err.message : String(err) });
2322
+ return `its declared backfill (${backfill}) does not evaluate on this database; check the expression's SQL and column names (snake_case)`;
2323
+ }
2324
+ }
2325
+ /**
2326
+ * #3008: the column definition a SQLite backfill rebuild appends — NOT
2327
+ * NULL, plus the declared default (for future inserts) and CHECK.
2328
+ */
2329
+ sqliteAddedColumnDefinition(change) {
2330
+ const column = change.column;
2331
+ const type = isValidSQLDataType(column.type) ? column.type : "TEXT";
2332
+ const parts = [
2333
+ this.quoteIdentifier(change.name),
2334
+ this.ddlStrategy.mapType(type),
2335
+ "NOT NULL"
2336
+ ];
2337
+ if (column.defaultValue !== void 0 && column.defaultValue !== null) parts.push(`DEFAULT ${this.ddlStrategy.formatDefaultValue(column.defaultValue, type)}`);
2338
+ if (column.check) parts.push(`CHECK (${column.check})`);
2339
+ return parts.join(" ");
2340
+ }
2341
+ /**
2342
+ * `ALTER COLUMN ... SET NOT NULL`, made executable on DuckDB. DuckDB
2343
+ * refuses to alter a table any index depends on ("Cannot alter entry …
2344
+ * because there are entries that depend on it"), so there the table's
2345
+ * explicit indexes are dropped around the ALTER and recreated from their
2346
+ * own DDL, inside the same migration unit.
2347
+ */
2348
+ async setNotNullStatements(tableName, colName, alterBefore = []) {
2349
+ const setNotNull = `ALTER TABLE ${this.quoteIdentifier(tableName)} ALTER COLUMN ${this.quoteIdentifier(colName)} SET NOT NULL`;
2350
+ if (this.engine !== "duckdb" && this.engine !== "json") return [...alterBefore, setNotNull];
2351
+ let indexes = [];
2352
+ try {
2353
+ indexes = (await this.db.query(`SELECT index_name, sql FROM duckdb_indexes() WHERE table_name = ${this.quoteLiteral(tableName)} AND sql IS NOT NULL`)).rows.flatMap((row) => typeof row.index_name === "string" && typeof row.sql === "string" ? [{
2354
+ name: row.index_name,
2355
+ sql: row.sql.replace(/;\s*$/, "")
2356
+ }] : []);
2357
+ } catch (err) {
2358
+ logger.debug(`[SchemaComparer] DuckDB index probe unavailable for ${tableName}`, { error: err instanceof Error ? err.message : String(err) });
2359
+ }
2360
+ for (const planned of this.batchIndexes.get(tableName) ?? []) if (!indexes.some((index) => index.name === planned.name)) indexes.push(planned);
2361
+ return [
2362
+ ...indexes.map((index) => `DROP INDEX ${this.quoteIdentifier(index.name)}`),
2363
+ ...alterBefore,
2364
+ setNotNull,
2365
+ ...indexes.map((index) => index.sql)
2366
+ ];
2367
+ }
2368
+ /**
2369
+ * #3008: the manual change reported instead of an `add_column` when a
2370
+ * required column without a default or backfill cannot be added to a
2371
+ * populated table (or when a SQLite backfill rebuild is refused). The
2372
+ * column is NOT added; `compareTable` also withholds the indexes and
2373
+ * foreign keys that would target it.
2374
+ */
2375
+ requiredColumnNotAddedChange(tableName, colName, colDef, reason) {
2376
+ const quotedTable = this.quoteIdentifier(tableName);
2377
+ const quotedCol = this.quoteIdentifier(colName);
2378
+ const why = reason ?? `it is required, has no default, and ${tableName} already holds rows`;
2379
+ const fix = reason ? "Fix that, then rerun db:migrate." : "Declare a per-row backfill on the field, e.g. @field({ required: true, backfill: \"'row:' || id\" }), or a default, then rerun db:migrate.";
2380
+ return {
2381
+ type: "alter_column",
2382
+ table: tableName,
2383
+ name: colName,
2384
+ column: colDef,
2385
+ alteration: "set_not_null",
2386
+ mismatch: {
2387
+ expected: "NOT NULL",
2388
+ actual: REQUIRED_COLUMN_NOT_ADDED
2389
+ },
2390
+ sql: `-- ${tableName}.${colName} was not added: ${why}. ${fix}`,
2391
+ advisory: {
2392
+ severity: "warning",
2393
+ message: `${tableName}.${colName} was not added: ${why}. ${fix}`,
2394
+ suggestedSql: [
2395
+ `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedCol} ${this.ddlStrategy.mapType(isValidSQLDataType(colDef.type) ? colDef.type : "TEXT")}`,
2396
+ `UPDATE ${quotedTable} SET ${quotedCol} = <per-row value> WHERE ${quotedCol} IS NULL`,
2397
+ this.supportsAlterColumn() ? `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET NOT NULL` : "-- SQLite: enforcing NOT NULL afterwards requires a table rebuild (#2370)"
2398
+ ]
2399
+ }
2400
+ };
2401
+ }
2402
+ /**
2170
2403
  * Generate SQL for dropping a column
2171
2404
  */
2172
2405
  generateDropColumnSQL(tableName, colName) {
@@ -2257,6 +2490,6 @@ function getSQLFromChanges(changes) {
2257
2490
  return statements;
2258
2491
  }
2259
2492
  //#endregion
2260
- export { SchemaComparer, canonicalizeDefault, extractIndexPredicate, generateSchemaDiff, getSQLFromChanges, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, normalizeIndexPredicate, uniqueColumnIndexName };
2493
+ export { REQUIRED_COLUMN_NOT_ADDED, SchemaComparer, canonicalizeDefault, extractIndexPredicate, generateSchemaDiff, getSQLFromChanges, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, normalizeIndexPredicate, uniqueColumnIndexName };
2261
2494
 
2262
2495
  //# sourceMappingURL=differ.js.map