@quereus/quereus 4.19.4 → 4.20.0
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/README.md +257 -257
- package/dist/src/common/errors.d.ts +10 -0
- package/dist/src/common/errors.d.ts.map +1 -1
- package/dist/src/common/errors.js +14 -0
- package/dist/src/common/errors.js.map +1 -1
- package/dist/src/common/types.d.ts +12 -0
- package/dist/src/common/types.d.ts.map +1 -1
- package/dist/src/common/types.js.map +1 -1
- package/dist/src/core/database-events.d.ts.map +1 -1
- package/dist/src/core/database-events.js +12 -9
- package/dist/src/core/database-events.js.map +1 -1
- package/dist/src/core/database-transaction.d.ts.map +1 -1
- package/dist/src/core/database-transaction.js +28 -19
- package/dist/src/core/database-transaction.js.map +1 -1
- package/dist/src/core/database.d.ts +58 -6
- package/dist/src/core/database.d.ts.map +1 -1
- package/dist/src/core/database.js +185 -54
- package/dist/src/core/database.js.map +1 -1
- package/dist/src/core/param.d.ts +46 -1
- package/dist/src/core/param.d.ts.map +1 -1
- package/dist/src/core/param.js +87 -7
- package/dist/src/core/param.js.map +1 -1
- package/dist/src/core/statement.d.ts +66 -1
- package/dist/src/core/statement.d.ts.map +1 -1
- package/dist/src/core/statement.js +156 -91
- package/dist/src/core/statement.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/parser/lexer.d.ts.map +1 -1
- package/dist/src/parser/lexer.js +9 -3
- package/dist/src/parser/lexer.js.map +1 -1
- package/dist/src/planner/analysis/change-scope.d.ts.map +1 -1
- package/dist/src/planner/analysis/change-scope.js +7 -20
- package/dist/src/planner/analysis/change-scope.js.map +1 -1
- package/dist/src/planner/analysis/check-extraction.d.ts +62 -0
- package/dist/src/planner/analysis/check-extraction.d.ts.map +1 -1
- package/dist/src/planner/analysis/check-extraction.js +19 -5
- package/dist/src/planner/analysis/check-extraction.js.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.d.ts.map +1 -1
- package/dist/src/planner/analysis/constraint-extractor.js +7 -0
- package/dist/src/planner/analysis/constraint-extractor.js.map +1 -1
- package/dist/src/planner/analysis/scalar-param-usage.d.ts +6 -3
- package/dist/src/planner/analysis/scalar-param-usage.d.ts.map +1 -1
- package/dist/src/planner/analysis/scalar-param-usage.js +32 -4
- package/dist/src/planner/analysis/scalar-param-usage.js.map +1 -1
- package/dist/src/planner/building/coercion.js +1 -1
- package/dist/src/planner/building/coercion.js.map +1 -1
- package/dist/src/planner/building/schema-authored-context.d.ts +7 -7
- package/dist/src/planner/building/schema-authored-context.js +7 -7
- package/dist/src/planner/nodes/scalar.d.ts +19 -1
- package/dist/src/planner/nodes/scalar.d.ts.map +1 -1
- package/dist/src/planner/nodes/scalar.js +13 -2
- package/dist/src/planner/nodes/scalar.js.map +1 -1
- package/dist/src/planner/nodes/set-operation-node.js +4 -0
- package/dist/src/planner/nodes/set-operation-node.js.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +44 -1
- package/dist/src/runtime/emit/add-constraint.js.map +1 -1
- package/dist/src/runtime/emit/alter-table.d.ts.map +1 -1
- package/dist/src/runtime/emit/alter-table.js +20 -32
- package/dist/src/runtime/emit/alter-table.js.map +1 -1
- package/dist/src/runtime/emit/ddl-event-scope.d.ts +20 -13
- package/dist/src/runtime/emit/ddl-event-scope.d.ts.map +1 -1
- package/dist/src/runtime/emit/ddl-event-scope.js +20 -13
- package/dist/src/runtime/emit/ddl-event-scope.js.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.d.ts.map +1 -1
- package/dist/src/runtime/emit/materialized-view-helpers.js +9 -4
- package/dist/src/runtime/emit/materialized-view-helpers.js.map +1 -1
- package/dist/src/runtime/emit/parameter.d.ts.map +1 -1
- package/dist/src/runtime/emit/parameter.js +13 -8
- package/dist/src/runtime/emit/parameter.js.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.d.ts +17 -6
- package/dist/src/runtime/emit/schema-declarative.d.ts.map +1 -1
- package/dist/src/runtime/emit/schema-declarative.js +169 -31
- package/dist/src/runtime/emit/schema-declarative.js.map +1 -1
- package/dist/src/schema/catalog-rendering.d.ts +26 -0
- package/dist/src/schema/catalog-rendering.d.ts.map +1 -1
- package/dist/src/schema/catalog-rendering.js +41 -4
- package/dist/src/schema/catalog-rendering.js.map +1 -1
- package/dist/src/schema/catalog.d.ts +23 -0
- package/dist/src/schema/catalog.d.ts.map +1 -1
- package/dist/src/schema/catalog.js +6 -4
- package/dist/src/schema/catalog.js.map +1 -1
- package/dist/src/schema/constraint-builder.d.ts +67 -11
- package/dist/src/schema/constraint-builder.d.ts.map +1 -1
- package/dist/src/schema/constraint-builder.js +115 -12
- package/dist/src/schema/constraint-builder.js.map +1 -1
- package/dist/src/schema/ddl-generator.d.ts +21 -0
- package/dist/src/schema/ddl-generator.d.ts.map +1 -1
- package/dist/src/schema/ddl-generator.js +7 -1
- package/dist/src/schema/ddl-generator.js.map +1 -1
- package/dist/src/schema/rename/self-qualifier-strip.d.ts +27 -0
- package/dist/src/schema/rename/self-qualifier-strip.d.ts.map +1 -1
- package/dist/src/schema/rename/self-qualifier-strip.js +73 -0
- package/dist/src/schema/rename/self-qualifier-strip.js.map +1 -1
- package/dist/src/schema/rename-rewriter.d.ts +5 -5
- package/dist/src/schema/rename-rewriter.d.ts.map +1 -1
- package/dist/src/schema/rename-rewriter.js +5 -5
- package/dist/src/schema/rename-rewriter.js.map +1 -1
- package/dist/src/schema/schema-differ.d.ts +41 -1
- package/dist/src/schema/schema-differ.d.ts.map +1 -1
- package/dist/src/schema/schema-differ.js +529 -36
- package/dist/src/schema/schema-differ.js.map +1 -1
- package/dist/src/types/temporal-ops.d.ts.map +1 -1
- package/dist/src/types/temporal-ops.js +16 -10
- package/dist/src/types/temporal-ops.js.map +1 -1
- package/dist/src/vtab/capabilities.d.ts +5 -2
- package/dist/src/vtab/capabilities.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.d.ts +15 -11
- package/dist/src/vtab/memory/layer/manager.d.ts.map +1 -1
- package/dist/src/vtab/memory/layer/manager.js +15 -11
- package/dist/src/vtab/memory/layer/manager.js.map +1 -1
- package/dist/src/vtab/module.d.ts +16 -0
- package/dist/src/vtab/module.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -6,6 +6,7 @@ import { createLogger } from '../common/logger.js';
|
|
|
6
6
|
import { validateReservedTags } from './reserved-tags.js';
|
|
7
7
|
import { raiseReservedTagDiagnostics } from './reserved-tags-policy.js';
|
|
8
8
|
import { renameColumnInAst, renameColumnInCheckExpression, renameTableInAst, tableReferencedInAst, objectRefKey, singleSchemaObjectRefResolver } from './rename-rewriter.js';
|
|
9
|
+
import { Parser } from '../parser/parser.js';
|
|
9
10
|
import { cloneExpr, cloneQueryExpr } from '../planner/mutation/scope-transform.js';
|
|
10
11
|
import { normalizeCollationName } from '../util/comparison.js';
|
|
11
12
|
import { inferType } from '../types/registry.js';
|
|
@@ -2374,8 +2375,8 @@ function orderDropsByFKDependency(dropSet, actualTables) {
|
|
|
2374
2375
|
*/
|
|
2375
2376
|
function comparisonDomainAlters(quotedTable, quotedCol, change) {
|
|
2376
2377
|
const prefix = `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol}`;
|
|
2377
|
-
const retype = change.dataType !== undefined ? [`${prefix} SET DATA TYPE ${change.dataType}
|
|
2378
|
-
const recollate = change.collation !== undefined ? [`${prefix} SET COLLATE ${change.collation}
|
|
2378
|
+
const retype = change.dataType !== undefined ? [{ sql: `${prefix} SET DATA TYPE ${change.dataType}`, attribute: 'dataType' }] : [];
|
|
2379
|
+
const recollate = change.collation !== undefined ? [{ sql: `${prefix} SET COLLATE ${change.collation}`, attribute: 'collation' }] : [];
|
|
2379
2380
|
return normalizeCollationName(change.collation ?? 'BINARY') === 'BINARY'
|
|
2380
2381
|
? [...recollate, ...retype]
|
|
2381
2382
|
: [...retype, ...recollate];
|
|
@@ -2399,12 +2400,31 @@ export function generateMigrationDDL(diff, schemaName) {
|
|
|
2399
2400
|
* them without re-lexing the text this function just produced. The remaining steps
|
|
2400
2401
|
* are short template-built strings (renames, drops, column/constraint alters,
|
|
2401
2402
|
* `SET TAGS`) and stay text-only.
|
|
2403
|
+
*
|
|
2404
|
+
* With `actual` — the pre-apply catalog `computeSchemaDiff` was handed — every step
|
|
2405
|
+
* also carries its reversal (`undo` / `irreversible`, see {@link MigrationStep}),
|
|
2406
|
+
* rendered by {@link UndoRenderer} at the step's own push site so a forward
|
|
2407
|
+
* statement and its undo read together. Without it nothing about the undo is
|
|
2408
|
+
* computed: `generateMigrationDDL` never passes a catalog, so `diff schema` output
|
|
2409
|
+
* and cost are untouched.
|
|
2402
2410
|
*/
|
|
2403
|
-
export function generateMigrationPlan(diff, schemaName) {
|
|
2411
|
+
export function generateMigrationPlan(diff, schemaName, actual) {
|
|
2404
2412
|
const statements = [];
|
|
2405
2413
|
const schemaPrefix = (schemaName && schemaName !== 'main') ? `${quoteIdentifier(schemaName)}.` : '';
|
|
2406
|
-
|
|
2407
|
-
|
|
2414
|
+
const undo = actual ? new UndoRenderer(schemaName ?? 'main', schemaPrefix, actual) : undefined;
|
|
2415
|
+
/**
|
|
2416
|
+
* Appends a template-built step — text-only, so the apply path parses it as
|
|
2417
|
+
* before. `undoOf` runs only when undo was requested, and renders the reversal
|
|
2418
|
+
* for the catalog state THIS step runs in (Rule 1 of {@link MigrationStep.undo}).
|
|
2419
|
+
*/
|
|
2420
|
+
const pushText = (sql, undoOf) => {
|
|
2421
|
+
statements.push(undo ? { sql, ...undoOf(undo) } : { sql });
|
|
2422
|
+
};
|
|
2423
|
+
/** Appends a create bucket; each create undoes to the `DROP … IF EXISTS` of what it made. */
|
|
2424
|
+
const pushCreates = (bucket, kind) => {
|
|
2425
|
+
for (const create of bucket)
|
|
2426
|
+
statements.push(undo ? { ...create, ...undo.dropCreated(create, kind) } : create);
|
|
2427
|
+
};
|
|
2408
2428
|
// Renames first — they free old names for subsequent creates and re-target
|
|
2409
2429
|
// dependents (handled inside ALTER TABLE ... RENAME by the rename rewriter).
|
|
2410
2430
|
// Only tables have a rename primitive. The other RenameOp kinds are metadata
|
|
@@ -2415,13 +2435,16 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2415
2435
|
// the table-alter channel (RENAME CONSTRAINT below).
|
|
2416
2436
|
for (const r of diff.renames) {
|
|
2417
2437
|
if (r.kind === 'table') {
|
|
2418
|
-
pushText(`ALTER TABLE ${schemaPrefix}${quoteIdentifier(r.oldName)} RENAME TO ${quoteIdentifier(r.newName)}`);
|
|
2438
|
+
pushText(`ALTER TABLE ${schemaPrefix}${quoteIdentifier(r.oldName)} RENAME TO ${quoteIdentifier(r.newName)}`, () => ({ undo: [`ALTER TABLE ${schemaPrefix}${quoteIdentifier(r.newName)} RENAME TO ${quoteIdentifier(r.oldName)}`] }));
|
|
2419
2439
|
}
|
|
2420
2440
|
// Non-table rename ops emit no DDL here — see the note above.
|
|
2421
2441
|
}
|
|
2442
|
+
// Every later step runs — and is undone — with the table renames in force, so
|
|
2443
|
+
// from here on the undo renderer reads the pre-apply catalog through them.
|
|
2444
|
+
undo?.tableRenamesLanded(diff.renames);
|
|
2422
2445
|
// Drop assertions first (they may reference tables)
|
|
2423
2446
|
for (const name of diff.assertionsToDrop) {
|
|
2424
|
-
pushText(`DROP ASSERTION IF EXISTS ${schemaPrefix}${quoteIdentifier(name)}
|
|
2447
|
+
pushText(`DROP ASSERTION IF EXISTS ${schemaPrefix}${quoteIdentifier(name)}`, u => u.recreateAssertion(name));
|
|
2425
2448
|
}
|
|
2426
2449
|
// Detach maintained tables (`drop maintained`) EARLY — where MV drops ran
|
|
2427
2450
|
// before — so a detach precedes any column op on that table AND any drop of its
|
|
@@ -2430,25 +2453,25 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2430
2453
|
// flip's cross-table detach-v2 / attach-v1 pair falls out of this ordering.
|
|
2431
2454
|
for (const alter of diff.tablesToAlter) {
|
|
2432
2455
|
if (alter.dropMaintained) {
|
|
2433
|
-
pushText(`ALTER TABLE ${schemaPrefix}${quoteIdentifier(alter.tableName)} DROP MAINTAINED
|
|
2456
|
+
pushText(`ALTER TABLE ${schemaPrefix}${quoteIdentifier(alter.tableName)} DROP MAINTAINED`, u => u.reattachAsBefore(alter.tableName));
|
|
2434
2457
|
}
|
|
2435
2458
|
}
|
|
2436
2459
|
// Drop items (reverse order)
|
|
2437
2460
|
for (const tableName of diff.tablesToDrop) {
|
|
2438
|
-
pushText(`DROP TABLE IF EXISTS ${schemaPrefix}${quoteIdentifier(tableName)}
|
|
2461
|
+
pushText(`DROP TABLE IF EXISTS ${schemaPrefix}${quoteIdentifier(tableName)}`, u => u.droppedTable(tableName));
|
|
2439
2462
|
}
|
|
2440
2463
|
for (const viewName of diff.viewsToDrop) {
|
|
2441
|
-
pushText(`DROP VIEW IF EXISTS ${schemaPrefix}${quoteIdentifier(viewName)}
|
|
2464
|
+
pushText(`DROP VIEW IF EXISTS ${schemaPrefix}${quoteIdentifier(viewName)}`, u => u.recreateView(viewName));
|
|
2442
2465
|
}
|
|
2443
2466
|
for (const indexName of diff.indexesToDrop) {
|
|
2444
|
-
pushText(`DROP INDEX IF EXISTS ${schemaPrefix}${quoteIdentifier(indexName)}
|
|
2467
|
+
pushText(`DROP INDEX IF EXISTS ${schemaPrefix}${quoteIdentifier(indexName)}`, u => u.recreateIndex(indexName));
|
|
2445
2468
|
}
|
|
2446
2469
|
// Create new items. A fresh maintained table rides `tablesToCreate` (rendered
|
|
2447
2470
|
// as the `create materialized view` sugar) and re-materializes as part of its
|
|
2448
2471
|
// create.
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2472
|
+
pushCreates(diff.tablesToCreate, 'table');
|
|
2473
|
+
pushCreates(diff.viewsToCreate, 'view');
|
|
2474
|
+
pushCreates(diff.indexesToCreate, 'index');
|
|
2452
2475
|
// Assertion creates do NOT go here — they run last, after the table alters and
|
|
2453
2476
|
// the maintained re-attaches. See the push at the end of this function.
|
|
2454
2477
|
// Alter existing tables.
|
|
@@ -2468,47 +2491,56 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2468
2491
|
for (const alter of diff.tablesToAlter) {
|
|
2469
2492
|
const quotedTable = `${schemaPrefix}${quoteIdentifier(alter.tableName)}`;
|
|
2470
2493
|
for (const r of alter.columnsToRename) {
|
|
2471
|
-
pushText(`ALTER TABLE ${quotedTable} RENAME COLUMN ${quoteIdentifier(r.oldName)} TO ${quoteIdentifier(r.newName)}`);
|
|
2494
|
+
pushText(`ALTER TABLE ${quotedTable} RENAME COLUMN ${quoteIdentifier(r.oldName)} TO ${quoteIdentifier(r.newName)}`, () => ({ undo: [`ALTER TABLE ${quotedTable} RENAME COLUMN ${quoteIdentifier(r.newName)} TO ${quoteIdentifier(r.oldName)}`] }));
|
|
2495
|
+
undo?.columnRenameLanded(alter.tableName, r);
|
|
2472
2496
|
}
|
|
2473
2497
|
for (const colDef of alter.columnsToAdd) {
|
|
2474
|
-
pushText(`ALTER TABLE ${quotedTable} ADD COLUMN ${colDef}
|
|
2498
|
+
pushText(`ALTER TABLE ${quotedTable} ADD COLUMN ${colDef}`, u => u.dropAddedColumn(quotedTable, colDef));
|
|
2475
2499
|
}
|
|
2476
2500
|
for (const colAlter of alter.columnsToAlter) {
|
|
2477
2501
|
const quotedCol = quoteIdentifier(colAlter.columnName);
|
|
2502
|
+
const alterColumn = `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol}`;
|
|
2478
2503
|
// A default the retype would choke on and this migration replaces anyway is
|
|
2479
2504
|
// cleared BEFORE the comparison-domain phase (see `dropStaleDefaultFirst`).
|
|
2480
2505
|
if (colAlter.dropStaleDefaultFirst) {
|
|
2481
|
-
pushText(
|
|
2506
|
+
pushText(`${alterColumn} DROP DEFAULT`, u => u.undoDropDefault(alter.tableName, colAlter.columnName, alterColumn));
|
|
2482
2507
|
}
|
|
2483
2508
|
// SET DATA TYPE / SET COLLATE lead the per-column phase (both are
|
|
2484
2509
|
// comparison-domain changes), before DEFAULT / NOT NULL.
|
|
2485
|
-
for (const s of comparisonDomainAlters(quotedTable, quotedCol, colAlter))
|
|
2486
|
-
pushText(s
|
|
2510
|
+
for (const s of comparisonDomainAlters(quotedTable, quotedCol, colAlter)) {
|
|
2511
|
+
pushText(s.sql, u => s.attribute === 'dataType'
|
|
2512
|
+
? u.retypedColumn(alter.tableName, colAlter.columnName)
|
|
2513
|
+
: u.undoSetCollation(alter.tableName, colAlter.columnName, alterColumn));
|
|
2514
|
+
}
|
|
2487
2515
|
if (colAlter.defaultValue !== undefined) {
|
|
2488
2516
|
if (colAlter.defaultValue === null) {
|
|
2489
2517
|
// Already dropped above when it preceded the retype.
|
|
2490
2518
|
if (!colAlter.dropStaleDefaultFirst) {
|
|
2491
|
-
pushText(
|
|
2519
|
+
pushText(`${alterColumn} DROP DEFAULT`, u => u.undoDropDefault(alter.tableName, colAlter.columnName, alterColumn));
|
|
2492
2520
|
}
|
|
2493
2521
|
}
|
|
2494
2522
|
else {
|
|
2495
|
-
|
|
2523
|
+
// After `dropStaleDefaultFirst` the column reaches this step with no
|
|
2524
|
+
// default — the stale one was cleared earlier in this phase — so the
|
|
2525
|
+
// reversal is a plain DROP DEFAULT, not the pre-apply default (Rule 1).
|
|
2526
|
+
pushText(`${alterColumn} SET DEFAULT ${expressionToString(colAlter.defaultValue)}`, u => colAlter.dropStaleDefaultFirst
|
|
2527
|
+
? { undo: [`${alterColumn} DROP DEFAULT`] }
|
|
2528
|
+
: u.undoSetDefault(alter.tableName, colAlter.columnName, alterColumn));
|
|
2496
2529
|
}
|
|
2497
2530
|
}
|
|
2498
2531
|
if (colAlter.notNull !== undefined) {
|
|
2499
|
-
pushText(colAlter.notNull
|
|
2500
|
-
? `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} SET NOT NULL`
|
|
2501
|
-
: `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedCol} DROP NOT NULL`);
|
|
2532
|
+
pushText(colAlter.notNull ? `${alterColumn} SET NOT NULL` : `${alterColumn} DROP NOT NULL`, () => ({ undo: [colAlter.notNull ? `${alterColumn} DROP NOT NULL` : `${alterColumn} SET NOT NULL`] }));
|
|
2502
2533
|
}
|
|
2503
2534
|
}
|
|
2504
2535
|
// Constraint lifecycle: RENAME (free a name) then DROP (remove a stale /
|
|
2505
2536
|
// conflicting constraint), both BEFORE re-adds and before the PK change so a
|
|
2506
2537
|
// dropped UNIQUE can't strand a PK dependency.
|
|
2507
2538
|
for (const r of alter.constraintsToRename ?? []) {
|
|
2508
|
-
pushText(`ALTER TABLE ${quotedTable} RENAME CONSTRAINT ${quoteIdentifier(r.oldName)} TO ${quoteIdentifier(r.newName)}`);
|
|
2539
|
+
pushText(`ALTER TABLE ${quotedTable} RENAME CONSTRAINT ${quoteIdentifier(r.oldName)} TO ${quoteIdentifier(r.newName)}`, () => ({ undo: [`ALTER TABLE ${quotedTable} RENAME CONSTRAINT ${quoteIdentifier(r.newName)} TO ${quoteIdentifier(r.oldName)}`] }));
|
|
2540
|
+
undo?.constraintRenameLanded(alter.tableName, r);
|
|
2509
2541
|
}
|
|
2510
2542
|
for (const name of alter.constraintsToDrop ?? []) {
|
|
2511
|
-
pushText(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quoteIdentifier(name)}
|
|
2543
|
+
pushText(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quoteIdentifier(name)}`, u => u.readdConstraint(alter.tableName, name, quotedTable));
|
|
2512
2544
|
}
|
|
2513
2545
|
if (alter.primaryKeyChange) {
|
|
2514
2546
|
const pkCols = alter.primaryKeyChange.newPkColumns
|
|
@@ -2519,16 +2551,16 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2519
2551
|
return s;
|
|
2520
2552
|
})
|
|
2521
2553
|
.join(', ');
|
|
2522
|
-
pushText(`ALTER TABLE ${quotedTable} ALTER PRIMARY KEY (${pkCols})
|
|
2554
|
+
pushText(`ALTER TABLE ${quotedTable} ALTER PRIMARY KEY (${pkCols})`, u => u.restorePrimaryKey(alter.tableName, quotedTable));
|
|
2523
2555
|
}
|
|
2524
2556
|
// ADD CONSTRAINT after the PK change (a new UNIQUE / FK may align with the new
|
|
2525
2557
|
// key) and after the column adds it may reference. CHECK adds apply in-place;
|
|
2526
2558
|
// UNIQUE / FK adds depend on module ADD CONSTRAINT support (see constraintsToAdd).
|
|
2527
2559
|
for (const frag of alter.constraintsToAdd ?? []) {
|
|
2528
|
-
pushText(`ALTER TABLE ${quotedTable} ADD ${frag}
|
|
2560
|
+
pushText(`ALTER TABLE ${quotedTable} ADD ${frag}`, u => u.dropAddedConstraint(quotedTable, frag));
|
|
2529
2561
|
}
|
|
2530
2562
|
for (const colName of alter.columnsToDrop) {
|
|
2531
|
-
pushText(`ALTER TABLE ${quotedTable} DROP COLUMN ${quoteIdentifier(colName)}
|
|
2563
|
+
pushText(`ALTER TABLE ${quotedTable} DROP COLUMN ${quoteIdentifier(colName)}`, u => u.droppedColumn(alter.tableName, colName));
|
|
2532
2564
|
}
|
|
2533
2565
|
// Tags phase — last, so a SET TAGS lands on the post-structural column /
|
|
2534
2566
|
// constraint set (a tag set emitted alongside a RENAME COLUMN targets the
|
|
@@ -2539,15 +2571,17 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2539
2571
|
// TABLE handler rejects a tag action on a maintained table). See
|
|
2540
2572
|
// `TableAlterDiff.maintainedTags`.
|
|
2541
2573
|
const tagVerb = alter.maintainedTags ? 'ALTER MATERIALIZED VIEW' : 'ALTER TABLE';
|
|
2542
|
-
pushText(`${tagVerb} ${quotedTable} SET TAGS ${tagsBodyToString(alter.tableTagsChange)}`);
|
|
2574
|
+
pushText(`${tagVerb} ${quotedTable} SET TAGS ${tagsBodyToString(alter.tableTagsChange)}`, u => u.restoreTableTags(alter.tableName, `${tagVerb} ${quotedTable}`));
|
|
2543
2575
|
}
|
|
2544
2576
|
for (const colAlter of alter.columnsToAlter) {
|
|
2545
2577
|
if (colAlter.tags === undefined)
|
|
2546
2578
|
continue;
|
|
2547
|
-
|
|
2579
|
+
const alterColumn = `ALTER TABLE ${quotedTable} ALTER COLUMN ${quoteIdentifier(colAlter.columnName)}`;
|
|
2580
|
+
pushText(`${alterColumn} SET TAGS ${tagsBodyToString(colAlter.tags)}`, u => u.restoreColumnTags(alter.tableName, colAlter.columnName, alterColumn));
|
|
2548
2581
|
}
|
|
2549
2582
|
for (const ctc of alter.constraintTagsChanges ?? []) {
|
|
2550
|
-
|
|
2583
|
+
const alterConstraint = `ALTER TABLE ${quotedTable} ALTER CONSTRAINT ${quoteIdentifier(ctc.constraintName)}`;
|
|
2584
|
+
pushText(`${alterConstraint} SET TAGS ${tagsBodyToString(ctc.tags)}`, u => u.restoreConstraintTags(alter.tableName, ctc.constraintName, alterConstraint));
|
|
2551
2585
|
}
|
|
2552
2586
|
}
|
|
2553
2587
|
// Attach / re-attach maintained tables (`set maintained as`) LATE — where MV
|
|
@@ -2575,7 +2609,8 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2575
2609
|
select: alter.setMaintained.select,
|
|
2576
2610
|
},
|
|
2577
2611
|
};
|
|
2578
|
-
|
|
2612
|
+
const step = { sql: astToString(stmt), ast: stmt };
|
|
2613
|
+
statements.push(undo ? { ...step, ...undo.undoSetMaintained(alter) } : step);
|
|
2579
2614
|
}
|
|
2580
2615
|
// Assertion creates LAST — after every table alter and maintained re-attach, so
|
|
2581
2616
|
// a body sees the final shape of every object. `CREATE ASSERTION` plans its
|
|
@@ -2584,7 +2619,7 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2584
2619
|
// create ran in the create block, before `ADD COLUMN`. Nothing in a migration
|
|
2585
2620
|
// depends on an assertion existing, so last is strictly safer. Assertion DROPs
|
|
2586
2621
|
// stay first (see the top of this function).
|
|
2587
|
-
|
|
2622
|
+
pushCreates(diff.assertionsToCreate, 'assertion');
|
|
2588
2623
|
// In-place tag changes on views / indexes. These are leaf metadata writes (no
|
|
2589
2624
|
// dependency ordering vs the table-alter block). The `?? []` keeps
|
|
2590
2625
|
// generateMigrationDDL robust against hand-built diffs (some tests construct
|
|
@@ -2592,11 +2627,469 @@ export function generateMigrationPlan(diff, schemaName) {
|
|
|
2592
2627
|
// table's tag-only change rides the table-alter block above (`SET TAGS`), not a
|
|
2593
2628
|
// separate bucket.
|
|
2594
2629
|
for (const vtc of diff.viewTagsChanges ?? []) {
|
|
2595
|
-
|
|
2630
|
+
const alterView = `ALTER VIEW ${schemaPrefix}${quoteIdentifier(vtc.name)}`;
|
|
2631
|
+
pushText(`${alterView} SET TAGS ${tagsBodyToString(vtc.tags)}`, u => u.restoreViewTags(vtc.name, alterView));
|
|
2596
2632
|
}
|
|
2597
2633
|
for (const itc of diff.indexTagsChanges ?? []) {
|
|
2598
|
-
|
|
2634
|
+
const alterIndex = `ALTER INDEX ${schemaPrefix}${quoteIdentifier(itc.name)}`;
|
|
2635
|
+
pushText(`${alterIndex} SET TAGS ${tagsBodyToString(itc.tags)}`, u => u.restoreIndexTags(itc.name, alterIndex));
|
|
2599
2636
|
}
|
|
2600
2637
|
return statements;
|
|
2601
2638
|
}
|
|
2639
|
+
/** A step that changed nothing, so nothing has to be put back. */
|
|
2640
|
+
const NOTHING_TO_UNDO = { undo: [] };
|
|
2641
|
+
/** Case-insensitive identifier equality — how the catalog and the rename walkers compare names. */
|
|
2642
|
+
const sameName = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
2643
|
+
/**
|
|
2644
|
+
* The pre-apply spelling of a name as spelled NOW, given the renames in force:
|
|
2645
|
+
* the old name when a rename landed on it, the name itself when none touched it,
|
|
2646
|
+
* and `undefined` when a rename vacated it — nothing pre-apply answers to it any more.
|
|
2647
|
+
*/
|
|
2648
|
+
function preRenameName(renames, currentName) {
|
|
2649
|
+
const landed = renames.find(r => sameName(r.newName, currentName));
|
|
2650
|
+
if (landed)
|
|
2651
|
+
return landed.oldName;
|
|
2652
|
+
return renames.some(r => sameName(r.oldName, currentName)) ? undefined : currentName;
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Renders each migration step's undo from the pre-apply catalog. The two rules
|
|
2656
|
+
* {@link MigrationStep.undo} states are what every arm below honours:
|
|
2657
|
+
*
|
|
2658
|
+
* **Rule 1 — spell the target the way the forward step did.** The unwind runs in
|
|
2659
|
+
* reverse, so when a step's undo runs every later step has already been taken back
|
|
2660
|
+
* and the catalog is in the state the step itself left behind. The plan is pushed in
|
|
2661
|
+
* execution order and this renderer is told about each rename as its step lands
|
|
2662
|
+
* (`tableRenamesLanded`, `columnRenameLanded`, `constraintRenameLanded`), so every
|
|
2663
|
+
* catalog lookup goes through exactly the renames in force at that step — a later
|
|
2664
|
+
* `ALTER TABLE t …` step's undo names the post-rename `t`, and finds the pre-apply
|
|
2665
|
+
* table under its old name.
|
|
2666
|
+
*
|
|
2667
|
+
* **Rule 2 — forward-apply the renames in force to anything taken from the catalog.**
|
|
2668
|
+
* A restored constraint / view / assertion / derivation body, a column default and
|
|
2669
|
+
* the old primary key are spelled with pre-rename names in the catalog; by Rule 1
|
|
2670
|
+
* those renames are still in force when the undo runs, so {@link replayRenames}
|
|
2671
|
+
* applies them forward — through the same `renameTableInAst` /
|
|
2672
|
+
* `renameColumnInAst` / `renameColumnInCheckExpression` walkers the live rename
|
|
2673
|
+
* propagation uses — before the body is emitted. This is the differ's
|
|
2674
|
+
* rename-reconciled comparison (`reconciledDeclaredBody`) pointed the other way.
|
|
2675
|
+
* Only renames whose steps PRECEDE this one count: a view dropped before the
|
|
2676
|
+
* column-rename phase is restored with the old column names, because by the time
|
|
2677
|
+
* its undo runs the column rename has already been reversed.
|
|
2678
|
+
*
|
|
2679
|
+
* Two policies keep the plan generator total over hand-built inputs:
|
|
2680
|
+
* - a target the pre-apply catalog does not mention undoes to nothing — the
|
|
2681
|
+
* forward step either is a no-op (`IF EXISTS`) or fails before changing anything;
|
|
2682
|
+
* - a catalog entry that lacks what its restore needs (no body AST, DDL that does
|
|
2683
|
+
* not parse) is reported as `irreversible` with the reason, never thrown.
|
|
2684
|
+
*
|
|
2685
|
+
* NOTE: the undo is rendered eagerly for every step at plan time, so once the apply
|
|
2686
|
+
* path passes its catalog every apply pays it; the only non-trivial per-step cost is
|
|
2687
|
+
* one small parse on the fragment arms (dropped index, added column / constraint).
|
|
2688
|
+
* If plan generation ever shows up in profiles, render lazily on the failure path.
|
|
2689
|
+
*/
|
|
2690
|
+
class UndoRenderer {
|
|
2691
|
+
schemaName;
|
|
2692
|
+
schemaPrefix;
|
|
2693
|
+
tables = new Map();
|
|
2694
|
+
views = new Map();
|
|
2695
|
+
indexes = new Map();
|
|
2696
|
+
assertions = new Map();
|
|
2697
|
+
resolveRef;
|
|
2698
|
+
tableRenames = [];
|
|
2699
|
+
columnRenames = [];
|
|
2700
|
+
constraintRenames = [];
|
|
2701
|
+
constructor(schemaName, schemaPrefix, actual) {
|
|
2702
|
+
this.schemaName = schemaName;
|
|
2703
|
+
this.schemaPrefix = schemaPrefix;
|
|
2704
|
+
for (const t of actual.tables ?? [])
|
|
2705
|
+
this.tables.set(t.name.toLowerCase(), t);
|
|
2706
|
+
for (const v of actual.views ?? [])
|
|
2707
|
+
this.views.set(v.name.toLowerCase(), v);
|
|
2708
|
+
// NOTE: an index name is unique per table, not per schema, and the plan's
|
|
2709
|
+
// `DROP INDEX` names it bare — so the first entry wins here, as it does there.
|
|
2710
|
+
// If `indexesToDrop` ever carries the owning table, key this map by both.
|
|
2711
|
+
for (const i of actual.indexes ?? [])
|
|
2712
|
+
if (!this.indexes.has(i.name.toLowerCase()))
|
|
2713
|
+
this.indexes.set(i.name.toLowerCase(), i);
|
|
2714
|
+
for (const a of actual.assertions ?? [])
|
|
2715
|
+
this.assertions.set(a.name.toLowerCase(), a);
|
|
2716
|
+
this.resolveRef = singleSchemaObjectRefResolver(schemaName);
|
|
2717
|
+
}
|
|
2718
|
+
// --- Rename bookkeeping (Rule 1) ---
|
|
2719
|
+
tableRenamesLanded(renames) {
|
|
2720
|
+
this.tableRenames = renames.filter(r => r.kind === 'table');
|
|
2721
|
+
}
|
|
2722
|
+
columnRenameLanded(table, r) {
|
|
2723
|
+
this.columnRenames.push({ table, oldName: r.oldName, newName: r.newName });
|
|
2724
|
+
}
|
|
2725
|
+
constraintRenameLanded(table, r) {
|
|
2726
|
+
this.constraintRenames.push({ table, oldName: r.oldName, newName: r.newName });
|
|
2727
|
+
}
|
|
2728
|
+
// --- Arms ---
|
|
2729
|
+
dropCreated(create, kind) {
|
|
2730
|
+
const name = createdObjectName(create.ast, kind);
|
|
2731
|
+
if (name === undefined)
|
|
2732
|
+
return this.cannotUndo(create.sql, `its statement is not a create ${kind}`);
|
|
2733
|
+
const verb = { table: 'DROP TABLE', view: 'DROP VIEW', index: 'DROP INDEX', assertion: 'DROP ASSERTION' }[kind];
|
|
2734
|
+
return { undo: [`${verb} IF EXISTS ${this.schemaPrefix}${quoteIdentifier(name)}`] };
|
|
2735
|
+
}
|
|
2736
|
+
droppedTable(actualName) {
|
|
2737
|
+
if (!this.tables.has(actualName.toLowerCase()))
|
|
2738
|
+
return NOTHING_TO_UNDO;
|
|
2739
|
+
return { irreversible: `dropping table "${actualName}" also discards its rows, and no statement can bring them back` };
|
|
2740
|
+
}
|
|
2741
|
+
droppedColumn(table, column) {
|
|
2742
|
+
if (!this.columnNow(table, column))
|
|
2743
|
+
return NOTHING_TO_UNDO;
|
|
2744
|
+
return { irreversible: `dropping column "${column}" from table "${table}" discards the values stored in it, and no statement can bring them back` };
|
|
2745
|
+
}
|
|
2746
|
+
retypedColumn(table, column) {
|
|
2747
|
+
if (!this.columnNow(table, column))
|
|
2748
|
+
return NOTHING_TO_UNDO;
|
|
2749
|
+
return { irreversible: `changing the type of column "${column}" in table "${table}" converts the values stored in it, and a conversion can lose information that no statement can bring back` };
|
|
2750
|
+
}
|
|
2751
|
+
recreateAssertion(name) {
|
|
2752
|
+
const assertion = this.assertions.get(name.toLowerCase());
|
|
2753
|
+
if (!assertion)
|
|
2754
|
+
return NOTHING_TO_UNDO;
|
|
2755
|
+
if (!assertion.check)
|
|
2756
|
+
return this.cannotUndo(`DROP ASSERTION ${name}`, 'the pre-apply catalog carries no CHECK body for it');
|
|
2757
|
+
const check = cloneExpr(assertion.check);
|
|
2758
|
+
this.replayRenames(check, undefined, {});
|
|
2759
|
+
const stmt = { type: 'createAssertion', name: { type: 'identifier', name: assertion.name }, check };
|
|
2760
|
+
return { undo: [createAssertionToString(applyAssertionSchemaDefault(stmt, this.schemaName))] };
|
|
2761
|
+
}
|
|
2762
|
+
recreateView(name) {
|
|
2763
|
+
const view = this.views.get(name.toLowerCase());
|
|
2764
|
+
if (!view)
|
|
2765
|
+
return NOTHING_TO_UNDO;
|
|
2766
|
+
if (!view.select)
|
|
2767
|
+
return this.cannotUndo(`DROP VIEW ${name}`, 'the pre-apply catalog carries no body for it');
|
|
2768
|
+
const select = cloneQueryExpr(view.select);
|
|
2769
|
+
this.replayRenames(select, undefined, {});
|
|
2770
|
+
const stmt = {
|
|
2771
|
+
type: 'createView',
|
|
2772
|
+
view: { type: 'identifier', name: view.name },
|
|
2773
|
+
ifNotExists: false,
|
|
2774
|
+
columns: view.columns ? [...view.columns] : undefined,
|
|
2775
|
+
select,
|
|
2776
|
+
tags: view.tags ? { ...view.tags } : undefined,
|
|
2777
|
+
};
|
|
2778
|
+
return { undo: [createViewToString(applyViewSchemaDefault(stmt, this.schemaName))] };
|
|
2779
|
+
}
|
|
2780
|
+
recreateIndex(name) {
|
|
2781
|
+
const index = this.indexes.get(name.toLowerCase());
|
|
2782
|
+
if (!index)
|
|
2783
|
+
return NOTHING_TO_UNDO;
|
|
2784
|
+
// The catalog carries no structured index shape, only `generateIndexDDL`'s
|
|
2785
|
+
// text — re-parsed here with the real parser rather than sliced.
|
|
2786
|
+
const parsed = parseStatement(index.ddl);
|
|
2787
|
+
if (!parsed.ok)
|
|
2788
|
+
return this.cannotUndo(`DROP INDEX ${name}`, `its recorded DDL does not parse (${parsed.reason})`);
|
|
2789
|
+
if (parsed.stmt.type !== 'createIndex')
|
|
2790
|
+
return this.cannotUndo(`DROP INDEX ${name}`, 'its recorded DDL is not a create index statement');
|
|
2791
|
+
const recreate = {
|
|
2792
|
+
...parsed.stmt,
|
|
2793
|
+
ifNotExists: false,
|
|
2794
|
+
index: { type: 'identifier', name: index.name },
|
|
2795
|
+
table: { type: 'identifier', name: this.tableNameNow(index.tableName) },
|
|
2796
|
+
};
|
|
2797
|
+
if (recreate.where)
|
|
2798
|
+
this.replayRenames(recreate.where, undefined, { schemaAuthoredBody: true });
|
|
2799
|
+
return { undo: [createIndexToString(applyIndexDefaults(recreate, this.schemaName))] };
|
|
2800
|
+
}
|
|
2801
|
+
/** Undo of `DROP MAINTAINED`: the derivation the table carried before the apply. */
|
|
2802
|
+
reattachAsBefore(table) {
|
|
2803
|
+
const maintained = this.tableNow(table)?.maintained;
|
|
2804
|
+
if (!maintained)
|
|
2805
|
+
return NOTHING_TO_UNDO;
|
|
2806
|
+
return this.setMaintainedFrom(maintained, table);
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2809
|
+
* Undo of `SET MAINTAINED`. The table is plain at this step when it never was
|
|
2810
|
+
* maintained OR when this same plan detached it earlier (the reshape leg:
|
|
2811
|
+
* `drop maintained` → column ops → re-attach); either way the attach undoes to
|
|
2812
|
+
* a detach, and the earlier detach's own undo restores the original derivation.
|
|
2813
|
+
* A same-shape re-attach (no detach) replaced the derivation in place, so its
|
|
2814
|
+
* undo re-attaches the pre-apply one.
|
|
2815
|
+
*/
|
|
2816
|
+
undoSetMaintained(alter) {
|
|
2817
|
+
const actual = this.tableNow(alter.tableName);
|
|
2818
|
+
if (!actual)
|
|
2819
|
+
return NOTHING_TO_UNDO;
|
|
2820
|
+
if (!actual.maintained || alter.dropMaintained) {
|
|
2821
|
+
return { undo: [`ALTER TABLE ${this.schemaPrefix}${quoteIdentifier(alter.tableName)} DROP MAINTAINED`] };
|
|
2822
|
+
}
|
|
2823
|
+
return this.setMaintainedFrom(actual.maintained, alter.tableName);
|
|
2824
|
+
}
|
|
2825
|
+
readdConstraint(table, name, quotedTable) {
|
|
2826
|
+
// `constraintsToDrop` carries pre-apply names (a renamed constraint is never
|
|
2827
|
+
// also dropped), so the lookup is direct rather than through the renames.
|
|
2828
|
+
const constraint = this.tableNow(table)?.namedConstraints?.find(c => sameName(c.name, name));
|
|
2829
|
+
if (!constraint)
|
|
2830
|
+
return NOTHING_TO_UNDO;
|
|
2831
|
+
if (!constraint.bodyAst)
|
|
2832
|
+
return this.cannotUndo(`DROP CONSTRAINT ${name}`, 'the pre-apply catalog carries no body for it');
|
|
2833
|
+
const body = this.forwardRenamedConstraint(constraint.bodyAst, table);
|
|
2834
|
+
const fragment = tableConstraintsToString([{ ...body, name: constraint.name, tags: constraint.tags ? { ...constraint.tags } : undefined }]);
|
|
2835
|
+
return { undo: [`ALTER TABLE ${quotedTable} ADD ${fragment}`] };
|
|
2836
|
+
}
|
|
2837
|
+
restorePrimaryKey(table, quotedTable) {
|
|
2838
|
+
const actual = this.tableNow(table);
|
|
2839
|
+
if (!actual)
|
|
2840
|
+
return NOTHING_TO_UNDO;
|
|
2841
|
+
if (actual.primaryKey.length === 0)
|
|
2842
|
+
return this.cannotUndo(`ALTER PRIMARY KEY on ${table}`, 'the pre-apply catalog records no primary key for it');
|
|
2843
|
+
const cols = actual.primaryKey
|
|
2844
|
+
.map(pk => quoteIdentifier(this.columnNameNow(table, pk.columnName)) + (pk.desc ? ' desc' : ''))
|
|
2845
|
+
.join(', ');
|
|
2846
|
+
return { undo: [`ALTER TABLE ${quotedTable} ALTER PRIMARY KEY (${cols})`] };
|
|
2847
|
+
}
|
|
2848
|
+
dropAddedColumn(quotedTable, colDef) {
|
|
2849
|
+
const parsed = parseStatement(`alter table "x" add column ${colDef}`);
|
|
2850
|
+
if (!parsed.ok)
|
|
2851
|
+
return this.cannotUndo(`ADD COLUMN ${colDef}`, `the column definition does not parse (${parsed.reason})`);
|
|
2852
|
+
const action = parsed.stmt.type === 'alterTable' ? parsed.stmt.action : undefined;
|
|
2853
|
+
if (action?.type !== 'addColumn')
|
|
2854
|
+
return this.cannotUndo(`ADD COLUMN ${colDef}`, 'the column definition does not parse as one');
|
|
2855
|
+
return { undo: [`ALTER TABLE ${quotedTable} DROP COLUMN ${quoteIdentifier(action.column.name)}`] };
|
|
2856
|
+
}
|
|
2857
|
+
dropAddedConstraint(quotedTable, fragment) {
|
|
2858
|
+
const parsed = parseStatement(`alter table "x" add ${fragment}`);
|
|
2859
|
+
if (!parsed.ok)
|
|
2860
|
+
return this.cannotUndo(`ADD ${fragment}`, `the constraint fragment does not parse (${parsed.reason})`);
|
|
2861
|
+
const action = parsed.stmt.type === 'alterTable' ? parsed.stmt.action : undefined;
|
|
2862
|
+
if (action?.type !== 'addConstraint')
|
|
2863
|
+
return this.cannotUndo(`ADD ${fragment}`, 'the fragment does not parse as a constraint');
|
|
2864
|
+
if (!action.constraint.name)
|
|
2865
|
+
return this.cannotUndo(`ADD ${fragment}`, 'the constraint has no name to drop it by');
|
|
2866
|
+
return { undo: [`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quoteIdentifier(action.constraint.name)}`] };
|
|
2867
|
+
}
|
|
2868
|
+
/** Undo of `DROP DEFAULT`: put the pre-apply default back, or nothing when there was none. */
|
|
2869
|
+
undoDropDefault(table, column, alterColumn) {
|
|
2870
|
+
const prior = this.priorDefaultText(table, column);
|
|
2871
|
+
return { undo: prior ? [`${alterColumn} SET DEFAULT ${prior}`] : [] };
|
|
2872
|
+
}
|
|
2873
|
+
/** Undo of `SET DEFAULT e`: the pre-apply default, or `DROP DEFAULT` when the column had none. */
|
|
2874
|
+
undoSetDefault(table, column, alterColumn) {
|
|
2875
|
+
const prior = this.priorDefaultText(table, column);
|
|
2876
|
+
if (prior === undefined)
|
|
2877
|
+
return NOTHING_TO_UNDO;
|
|
2878
|
+
return { undo: [prior === null ? `${alterColumn} DROP DEFAULT` : `${alterColumn} SET DEFAULT ${prior}`] };
|
|
2879
|
+
}
|
|
2880
|
+
undoSetCollation(table, column, alterColumn) {
|
|
2881
|
+
const actual = this.columnNow(table, column);
|
|
2882
|
+
if (!actual)
|
|
2883
|
+
return NOTHING_TO_UNDO;
|
|
2884
|
+
return { undo: [`${alterColumn} SET COLLATE ${actual.collation || 'BINARY'}`] };
|
|
2885
|
+
}
|
|
2886
|
+
restoreTableTags(table, alterTable) {
|
|
2887
|
+
const actual = this.tableNow(table);
|
|
2888
|
+
return actual ? restoreTags(alterTable, actual.tags) : NOTHING_TO_UNDO;
|
|
2889
|
+
}
|
|
2890
|
+
restoreColumnTags(table, column, alterColumn) {
|
|
2891
|
+
const actual = this.columnNow(table, column);
|
|
2892
|
+
return actual ? restoreTags(alterColumn, actual.tags) : NOTHING_TO_UNDO;
|
|
2893
|
+
}
|
|
2894
|
+
restoreConstraintTags(table, constraint, alterConstraint) {
|
|
2895
|
+
const actual = this.constraintNow(table, constraint);
|
|
2896
|
+
return actual ? restoreTags(alterConstraint, actual.tags) : NOTHING_TO_UNDO;
|
|
2897
|
+
}
|
|
2898
|
+
restoreViewTags(view, alterView) {
|
|
2899
|
+
const actual = this.views.get(view.toLowerCase());
|
|
2900
|
+
return actual ? restoreTags(alterView, actual.tags) : NOTHING_TO_UNDO;
|
|
2901
|
+
}
|
|
2902
|
+
restoreIndexTags(index, alterIndex) {
|
|
2903
|
+
const actual = this.indexes.get(index.toLowerCase());
|
|
2904
|
+
return actual ? restoreTags(alterIndex, actual.tags) : NOTHING_TO_UNDO;
|
|
2905
|
+
}
|
|
2906
|
+
// --- Catalog lookups through the renames in force (Rule 1) ---
|
|
2907
|
+
/**
|
|
2908
|
+
* The pre-apply table a CURRENT table name denotes. A name a rename in force
|
|
2909
|
+
* vacated (and nothing was renamed onto) denotes no pre-apply table: whatever
|
|
2910
|
+
* sits under it now, this plan created.
|
|
2911
|
+
*/
|
|
2912
|
+
tableNow(currentName) {
|
|
2913
|
+
const actualName = preRenameName(this.tableRenames, currentName);
|
|
2914
|
+
return actualName === undefined ? undefined : this.tables.get(actualName.toLowerCase());
|
|
2915
|
+
}
|
|
2916
|
+
/** The current spelling of a pre-apply table name. */
|
|
2917
|
+
tableNameNow(actualName) {
|
|
2918
|
+
return this.tableRenames.find(r => sameName(r.oldName, actualName))?.newName ?? actualName;
|
|
2919
|
+
}
|
|
2920
|
+
/** The pre-apply column a CURRENT column name denotes (both names as spelled now); see `tableNow` for a vacated name. */
|
|
2921
|
+
columnNow(currentTable, currentColumn) {
|
|
2922
|
+
const table = this.tableNow(currentTable);
|
|
2923
|
+
if (!table)
|
|
2924
|
+
return undefined;
|
|
2925
|
+
const actualName = preRenameName(this.columnRenames.filter(r => sameName(r.table, currentTable)), currentColumn);
|
|
2926
|
+
return actualName === undefined ? undefined : table.columns.find(c => sameName(c.name, actualName));
|
|
2927
|
+
}
|
|
2928
|
+
/** The current spelling of a pre-apply column name. */
|
|
2929
|
+
columnNameNow(currentTable, actualColumn) {
|
|
2930
|
+
return this.columnRenames.find(r => sameName(r.table, currentTable) && sameName(r.oldName, actualColumn))?.newName ?? actualColumn;
|
|
2931
|
+
}
|
|
2932
|
+
constraintNow(currentTable, currentName) {
|
|
2933
|
+
const table = this.tableNow(currentTable);
|
|
2934
|
+
if (!table)
|
|
2935
|
+
return undefined;
|
|
2936
|
+
const actualName = preRenameName(this.constraintRenames.filter(r => sameName(r.table, currentTable)), currentName);
|
|
2937
|
+
return actualName === undefined ? undefined : table.namedConstraints?.find(c => sameName(c.name, actualName));
|
|
2938
|
+
}
|
|
2939
|
+
// --- Catalog-sourced bodies with the renames in force applied forward (Rule 2) ---
|
|
2940
|
+
setMaintainedFrom(maintained, table) {
|
|
2941
|
+
if (!maintained.select)
|
|
2942
|
+
return this.cannotUndo(`the derivation of ${table}`, 'the pre-apply catalog carries no body for it');
|
|
2943
|
+
const select = cloneQueryExpr(maintained.select);
|
|
2944
|
+
this.replayRenames(select, undefined, {});
|
|
2945
|
+
const stmt = {
|
|
2946
|
+
type: 'alterTable',
|
|
2947
|
+
table: this.schemaName !== 'main'
|
|
2948
|
+
? { type: 'identifier', name: table, schema: this.schemaName }
|
|
2949
|
+
: { type: 'identifier', name: table },
|
|
2950
|
+
action: { type: 'setMaintained', columns: maintained.columns, select },
|
|
2951
|
+
};
|
|
2952
|
+
return { undo: [astToString(stmt)] };
|
|
2953
|
+
}
|
|
2954
|
+
/** The pre-apply DEFAULT of a column, rendered: `null` when it had none, `undefined` when the column is unknown. */
|
|
2955
|
+
priorDefaultText(table, column) {
|
|
2956
|
+
const actual = this.columnNow(table, column);
|
|
2957
|
+
if (!actual)
|
|
2958
|
+
return undefined;
|
|
2959
|
+
if (!actual.defaultValue)
|
|
2960
|
+
return null;
|
|
2961
|
+
const expr = cloneExpr(actual.defaultValue);
|
|
2962
|
+
this.replayRenames(expr, table, { rowImageContext: true, schemaAuthoredBody: true });
|
|
2963
|
+
return expressionToString(expr);
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* A clone of a pre-apply constraint body with the renames in force applied
|
|
2967
|
+
* forward — the inverse of `reconciledDeclaredBody`, arm for arm: a CHECK's
|
|
2968
|
+
* expression through the walkers (owning-table column renames seeded, other
|
|
2969
|
+
* tables' by scope); a UNIQUE's column list; an FK's local list, its parent's
|
|
2970
|
+
* column list under the parent's CURRENT name, then the parent name itself.
|
|
2971
|
+
*/
|
|
2972
|
+
forwardRenamedConstraint(tc, owner) {
|
|
2973
|
+
switch (tc.type) {
|
|
2974
|
+
case 'check': {
|
|
2975
|
+
const clone = { ...tc, expr: tc.expr ? cloneExpr(tc.expr) : undefined };
|
|
2976
|
+
if (clone.expr)
|
|
2977
|
+
this.replayRenames(clone.expr, owner, { rowImageContext: true, schemaAuthoredBody: true });
|
|
2978
|
+
return clone;
|
|
2979
|
+
}
|
|
2980
|
+
case 'unique': {
|
|
2981
|
+
const clone = { ...tc, columns: tc.columns?.map(c => ({ ...c })) };
|
|
2982
|
+
this.forwardRenameColumnList(clone.columns, owner);
|
|
2983
|
+
return clone;
|
|
2984
|
+
}
|
|
2985
|
+
case 'foreignKey': {
|
|
2986
|
+
const clone = {
|
|
2987
|
+
...tc,
|
|
2988
|
+
columns: tc.columns?.map(c => ({ ...c })),
|
|
2989
|
+
foreignKey: tc.foreignKey
|
|
2990
|
+
? { ...tc.foreignKey, columns: tc.foreignKey.columns ? [...tc.foreignKey.columns] : undefined }
|
|
2991
|
+
: tc.foreignKey,
|
|
2992
|
+
};
|
|
2993
|
+
this.forwardRenameColumnList(clone.columns, owner);
|
|
2994
|
+
const fk = clone.foreignKey;
|
|
2995
|
+
// Renames are within-schema, so a cross-schema parent is left alone.
|
|
2996
|
+
if (fk && (!fk.schema || sameName(fk.schema, this.schemaName))) {
|
|
2997
|
+
const parentNow = this.tableNameNow(fk.table);
|
|
2998
|
+
if (fk.columns) {
|
|
2999
|
+
for (let i = 0; i < fk.columns.length; i++)
|
|
3000
|
+
fk.columns[i] = this.columnNameNow(parentNow, fk.columns[i]);
|
|
3001
|
+
}
|
|
3002
|
+
fk.table = parentNow;
|
|
3003
|
+
}
|
|
3004
|
+
return clone;
|
|
3005
|
+
}
|
|
3006
|
+
default:
|
|
3007
|
+
return tc;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
forwardRenameColumnList(columns, table) {
|
|
3011
|
+
for (const col of columns ?? [])
|
|
3012
|
+
col.name = this.columnNameNow(table, col.name);
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Applies the renames in force to a body in place, in plan order: every table
|
|
3016
|
+
* rename first (they are the plan's first steps), then each column rename as its
|
|
3017
|
+
* step landed. Each call goes through the walker the live propagation uses for
|
|
3018
|
+
* that body kind: a body OWNED by a table (a CHECK, a column default — `owner`
|
|
3019
|
+
* set) takes the owning table's renames through the seeded CHECK walker in
|
|
3020
|
+
* `'own'` row-image mode and other tables' renames through the plain scope walk
|
|
3021
|
+
* in `'foreign'` mode; a free body (a view, assertion or derivation body, an
|
|
3022
|
+
* index predicate) takes every rename through the plain walk in `'none'` mode.
|
|
3023
|
+
* `tableOpts` is the body kind's `renameTableInAst` options, exactly as the
|
|
3024
|
+
* matching `renameTableIn*` helper passes them.
|
|
3025
|
+
*/
|
|
3026
|
+
replayRenames(node, owner, tableOpts) {
|
|
3027
|
+
for (const r of this.tableRenames) {
|
|
3028
|
+
renameTableInAst(node, {
|
|
3029
|
+
oldName: r.oldName, newName: r.newName, schemaName: this.schemaName,
|
|
3030
|
+
resolve: this.resolveRef, resolveAfter: this.resolveRef,
|
|
3031
|
+
}, tableOpts);
|
|
3032
|
+
}
|
|
3033
|
+
this.columnRenames.forEach((r, replayedSoFar) => {
|
|
3034
|
+
const targetKey = objectRefKey(this.schemaName, r.table);
|
|
3035
|
+
const resolveColumn = this.columnResolverAfter(replayedSoFar);
|
|
3036
|
+
if (owner !== undefined && sameName(r.table, owner)) {
|
|
3037
|
+
renameColumnInCheckExpression(node, owner, r.oldName, r.newName, this.resolveRef, targetKey, 'own', resolveColumn);
|
|
3038
|
+
}
|
|
3039
|
+
else {
|
|
3040
|
+
renameColumnInAst(node, r.table, r.oldName, r.newName, this.resolveRef, targetKey, owner !== undefined ? 'foreign' : 'none', resolveColumn);
|
|
3041
|
+
}
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Column-existence resolver for the scope-aware walks in {@link replayRenames}:
|
|
3046
|
+
* "does this FROM source expose this column?" answered against the pre-apply
|
|
3047
|
+
* catalog with the first `applied` column renames in force — the world the
|
|
3048
|
+
* matching live rename statement ran in. Cross-schema sources and views answer
|
|
3049
|
+
* false, as the differ's declared-side resolver does (conservative: worst case a
|
|
3050
|
+
* ref that would have bound to an inner source is rewritten too).
|
|
3051
|
+
*/
|
|
3052
|
+
columnResolverAfter(applied) {
|
|
3053
|
+
const inForce = this.columnRenames.slice(0, applied);
|
|
3054
|
+
return (schema, table, column) => {
|
|
3055
|
+
if (schema !== this.schemaName.toLowerCase())
|
|
3056
|
+
return false;
|
|
3057
|
+
const actual = this.tableNow(table);
|
|
3058
|
+
if (!actual)
|
|
3059
|
+
return false;
|
|
3060
|
+
const onTable = inForce.filter(r => sameName(r.table, table));
|
|
3061
|
+
return actual.columns.some(c => sameName(onTable.find(r => sameName(r.oldName, c.name))?.newName ?? c.name, column));
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
cannotUndo(what, why) {
|
|
3065
|
+
warnLog(`No undo for migration step ${what}: ${why}`);
|
|
3066
|
+
return { irreversible: `${what} cannot be undone: ${why}` };
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
/** `<prefix> SET TAGS (<pre-apply tags>)`; an absent or empty set restores "no tags" as the explicit clear form. */
|
|
3070
|
+
function restoreTags(prefix, tags) {
|
|
3071
|
+
return { undo: [`${prefix} SET TAGS ${tagsBodyToString(tags ? { ...tags } : undefined)}`] };
|
|
3072
|
+
}
|
|
3073
|
+
/** The name a create statement gives the object it makes, when it is a create of `kind`. */
|
|
3074
|
+
function createdObjectName(ast, kind) {
|
|
3075
|
+
if (!ast)
|
|
3076
|
+
return undefined;
|
|
3077
|
+
switch (ast.type) {
|
|
3078
|
+
case 'createTable': return kind === 'table' ? ast.table.name : undefined;
|
|
3079
|
+
case 'createMaterializedView': return kind === 'table' ? ast.view.name : undefined;
|
|
3080
|
+
case 'createView': return kind === 'view' ? ast.view.name : undefined;
|
|
3081
|
+
case 'createIndex': return kind === 'index' ? ast.index.name : undefined;
|
|
3082
|
+
case 'createAssertion': return kind === 'assertion' ? ast.name.name : undefined;
|
|
3083
|
+
default: return undefined;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
/** A parse that reports failure as data: the undo renderer must stay total over hand-built input. */
|
|
3087
|
+
function parseStatement(sql) {
|
|
3088
|
+
try {
|
|
3089
|
+
return { ok: true, stmt: new Parser().parse(sql) };
|
|
3090
|
+
}
|
|
3091
|
+
catch (e) {
|
|
3092
|
+
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
2602
3095
|
//# sourceMappingURL=schema-differ.js.map
|