@happyvertical/smrt-core 0.51.11 → 0.51.12
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.
- package/agents/schema-paths.md +19 -6
- package/dist/class.d.ts +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +4 -2
- package/dist/class.js.map +1 -1
- package/dist/decorators/index.d.ts +19 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +64 -5
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +270 -37
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -2
- package/dist/migrations/sqlite-rebuild.d.ts +41 -0
- package/dist/migrations/sqlite-rebuild.d.ts.map +1 -1
- package/dist/migrations/sqlite-rebuild.js +68 -13
- package/dist/migrations/sqlite-rebuild.js.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/registry/db-cache-key.d.ts +47 -0
- package/dist/registry/db-cache-key.d.ts.map +1 -0
- package/dist/registry/db-cache-key.js +201 -0
- package/dist/registry/db-cache-key.js.map +1 -0
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +6 -1
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry.d.ts +3 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -6
- package/dist/registry.js.map +1 -1
- package/dist/scanner/types.d.ts +2 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/backfill.d.ts +16 -0
- package/dist/schema/backfill.d.ts.map +1 -0
- package/dist/schema/backfill.js +26 -0
- package/dist/schema/backfill.js.map +1 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +3 -0
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/types.d.ts +12 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +5 -5
- 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
|
|
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
|
|
660
|
-
*
|
|
661
|
-
*
|
|
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":"AAAA;;;;;GAKG;AAiCH,OAAO,KAAK,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
|
-
|
|
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`,
|
|
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:
|
|
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
|
|
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
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
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
|
-
|
|
2233
|
+
`${quotedTable}${quotedCol}`;
|
|
2131
2234
|
let enforceNotNull = false;
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
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(
|
|
2158
|
-
|
|
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
|