@supabase/pg-delta 1.0.0-alpha.21 → 1.0.0-alpha.23
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/dist/core/catalog.diff.js +4 -3
- package/dist/core/catalog.model.d.ts +8 -1
- package/dist/core/catalog.model.js +10 -8
- package/dist/core/expand-replace-dependencies.js +23 -0
- package/dist/core/integrations/filter/flatten.js +13 -0
- package/dist/core/objects/aggregate/aggregate.diff.js +16 -0
- package/dist/core/objects/aggregate/aggregate.model.d.ts +10 -0
- package/dist/core/objects/aggregate/aggregate.model.js +19 -1
- package/dist/core/objects/aggregate/changes/aggregate.base.d.ts +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.security-label.d.ts +28 -0
- package/dist/core/objects/aggregate/changes/aggregate.security-label.js +64 -0
- package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +2 -1
- package/dist/core/objects/base.model.d.ts +8 -0
- package/dist/core/objects/base.model.js +2 -0
- package/dist/core/objects/domain/changes/domain.base.d.ts +1 -1
- package/dist/core/objects/domain/changes/domain.security-label.d.ts +28 -0
- package/dist/core/objects/domain/changes/domain.security-label.js +61 -0
- package/dist/core/objects/domain/changes/domain.types.d.ts +2 -1
- package/dist/core/objects/domain/domain.diff.js +16 -0
- package/dist/core/objects/domain/domain.model.d.ts +10 -0
- package/dist/core/objects/domain/domain.model.js +19 -1
- package/dist/core/objects/event-trigger/changes/event-trigger.base.d.ts +1 -1
- package/dist/core/objects/event-trigger/changes/event-trigger.security-label.d.ts +28 -0
- package/dist/core/objects/event-trigger/changes/event-trigger.security-label.js +61 -0
- package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +2 -1
- package/dist/core/objects/event-trigger/event-trigger.diff.js +16 -0
- package/dist/core/objects/event-trigger/event-trigger.model.d.ts +10 -0
- package/dist/core/objects/event-trigger/event-trigger.model.js +19 -1
- package/dist/core/objects/extract-with-retry.d.ts +36 -0
- package/dist/core/objects/extract-with-retry.js +51 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.d.ts +1 -1
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.d.ts +28 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.js +61 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +2 -1
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.d.ts +22 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +20 -1
- package/dist/core/objects/index/index.diff.js +0 -1
- package/dist/core/objects/index/index.model.d.ts +2 -3
- package/dist/core/objects/index/index.model.js +17 -6
- package/dist/core/objects/materialized-view/changes/materialized-view.base.d.ts +1 -1
- package/dist/core/objects/materialized-view/changes/materialized-view.security-label.d.ts +28 -0
- package/dist/core/objects/materialized-view/changes/materialized-view.security-label.js +61 -0
- package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +2 -1
- package/dist/core/objects/materialized-view/materialized-view.diff.js +18 -0
- package/dist/core/objects/materialized-view/materialized-view.model.d.ts +24 -1
- package/dist/core/objects/materialized-view/materialized-view.model.js +40 -5
- package/dist/core/objects/procedure/changes/procedure.base.d.ts +1 -1
- package/dist/core/objects/procedure/changes/procedure.security-label.d.ts +28 -0
- package/dist/core/objects/procedure/changes/procedure.security-label.js +69 -0
- package/dist/core/objects/procedure/changes/procedure.types.d.ts +2 -1
- package/dist/core/objects/procedure/procedure.diff.js +16 -0
- package/dist/core/objects/procedure/procedure.model.d.ts +12 -1
- package/dist/core/objects/procedure/procedure.model.js +39 -5
- package/dist/core/objects/publication/changes/publication.base.d.ts +1 -1
- package/dist/core/objects/publication/changes/publication.security-label.d.ts +28 -0
- package/dist/core/objects/publication/changes/publication.security-label.js +61 -0
- package/dist/core/objects/publication/changes/publication.types.d.ts +2 -1
- package/dist/core/objects/publication/publication.diff.js +16 -0
- package/dist/core/objects/publication/publication.model.d.ts +14 -0
- package/dist/core/objects/publication/publication.model.js +20 -1
- package/dist/core/objects/rls-policy/rls-policy.diff.js +13 -1
- package/dist/core/objects/role/changes/role.base.d.ts +1 -1
- package/dist/core/objects/role/changes/role.security-label.d.ts +28 -0
- package/dist/core/objects/role/changes/role.security-label.js +61 -0
- package/dist/core/objects/role/changes/role.types.d.ts +2 -1
- package/dist/core/objects/role/role.diff.js +16 -0
- package/dist/core/objects/role/role.model.d.ts +10 -0
- package/dist/core/objects/role/role.model.js +29 -0
- package/dist/core/objects/rule/rule.model.d.ts +2 -1
- package/dist/core/objects/rule/rule.model.js +20 -3
- package/dist/core/objects/schema/changes/schema.base.d.ts +1 -1
- package/dist/core/objects/schema/changes/schema.security-label.d.ts +28 -0
- package/dist/core/objects/schema/changes/schema.security-label.js +61 -0
- package/dist/core/objects/schema/changes/schema.types.d.ts +2 -1
- package/dist/core/objects/schema/schema.diff.js +24 -1
- package/dist/core/objects/schema/schema.model.d.ts +10 -0
- package/dist/core/objects/schema/schema.model.js +18 -1
- package/dist/core/objects/security-label.types.d.ts +20 -0
- package/dist/core/objects/security-label.types.js +46 -0
- package/dist/core/objects/sequence/changes/sequence.base.d.ts +1 -1
- package/dist/core/objects/sequence/changes/sequence.security-label.d.ts +28 -0
- package/dist/core/objects/sequence/changes/sequence.security-label.js +61 -0
- package/dist/core/objects/sequence/changes/sequence.types.d.ts +2 -1
- package/dist/core/objects/sequence/sequence.diff.d.ts +2 -1
- package/dist/core/objects/sequence/sequence.diff.js +44 -4
- package/dist/core/objects/sequence/sequence.model.d.ts +10 -0
- package/dist/core/objects/sequence/sequence.model.js +19 -1
- package/dist/core/objects/subscription/changes/subscription.base.d.ts +1 -1
- package/dist/core/objects/subscription/changes/subscription.security-label.d.ts +28 -0
- package/dist/core/objects/subscription/changes/subscription.security-label.js +61 -0
- package/dist/core/objects/subscription/changes/subscription.types.d.ts +2 -1
- package/dist/core/objects/subscription/subscription.diff.js +16 -0
- package/dist/core/objects/subscription/subscription.model.d.ts +10 -0
- package/dist/core/objects/subscription/subscription.model.js +19 -1
- package/dist/core/objects/table/changes/table.alter.d.ts +12 -1
- package/dist/core/objects/table/changes/table.alter.js +20 -2
- package/dist/core/objects/table/changes/table.base.d.ts +1 -1
- package/dist/core/objects/table/changes/table.security-label.d.ts +63 -0
- package/dist/core/objects/table/changes/table.security-label.js +134 -0
- package/dist/core/objects/table/changes/table.types.d.ts +2 -1
- package/dist/core/objects/table/table.diff.js +68 -15
- package/dist/core/objects/table/table.model.d.ts +36 -1
- package/dist/core/objects/table/table.model.js +74 -7
- package/dist/core/objects/trigger/trigger.model.d.ts +2 -1
- package/dist/core/objects/trigger/trigger.model.js +20 -4
- package/dist/core/objects/type/composite-type/changes/composite-type.base.d.ts +1 -1
- package/dist/core/objects/type/composite-type/changes/composite-type.security-label.d.ts +28 -0
- package/dist/core/objects/type/composite-type/changes/composite-type.security-label.js +61 -0
- package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +2 -1
- package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -0
- package/dist/core/objects/type/composite-type/composite-type.model.d.ts +22 -0
- package/dist/core/objects/type/composite-type/composite-type.model.js +22 -2
- package/dist/core/objects/type/enum/changes/enum.base.d.ts +1 -1
- package/dist/core/objects/type/enum/changes/enum.security-label.d.ts +28 -0
- package/dist/core/objects/type/enum/changes/enum.security-label.js +61 -0
- package/dist/core/objects/type/enum/changes/enum.types.d.ts +2 -1
- package/dist/core/objects/type/enum/enum.diff.js +16 -0
- package/dist/core/objects/type/enum/enum.model.d.ts +10 -0
- package/dist/core/objects/type/enum/enum.model.js +20 -1
- package/dist/core/objects/type/range/changes/range.base.d.ts +1 -1
- package/dist/core/objects/type/range/changes/range.security-label.d.ts +28 -0
- package/dist/core/objects/type/range/changes/range.security-label.js +61 -0
- package/dist/core/objects/type/range/changes/range.types.d.ts +2 -1
- package/dist/core/objects/type/range/range.diff.js +16 -0
- package/dist/core/objects/type/range/range.model.d.ts +10 -0
- package/dist/core/objects/type/range/range.model.js +19 -1
- package/dist/core/objects/utils.d.ts +2 -0
- package/dist/core/objects/utils.js +6 -0
- package/dist/core/objects/view/changes/view.base.d.ts +1 -1
- package/dist/core/objects/view/changes/view.security-label.d.ts +28 -0
- package/dist/core/objects/view/changes/view.security-label.js +61 -0
- package/dist/core/objects/view/changes/view.types.d.ts +2 -1
- package/dist/core/objects/view/view.diff.js +13 -0
- package/dist/core/objects/view/view.model.d.ts +28 -1
- package/dist/core/objects/view/view.model.js +40 -5
- package/dist/core/plan/create.js +3 -1
- package/dist/core/plan/sql-format/fixtures.js +1 -0
- package/dist/core/plan/types.d.ts +8 -0
- package/dist/core/{post-diff-cycle-breaking.d.ts → post-diff-normalization.d.ts} +8 -1
- package/dist/core/post-diff-normalization.js +202 -0
- package/dist/core/sort/cycle-breakers.js +1 -1
- package/dist/core/sort/utils.d.ts +10 -0
- package/dist/core/sort/utils.js +28 -0
- package/package.json +1 -1
- package/src/core/catalog.diff.ts +4 -2
- package/src/core/catalog.model.ts +21 -8
- package/src/core/expand-replace-dependencies.test.ts +131 -0
- package/src/core/expand-replace-dependencies.ts +24 -0
- package/src/core/integrations/filter/dsl.test.ts +27 -0
- package/src/core/integrations/filter/flatten.ts +16 -0
- package/src/core/objects/aggregate/aggregate.diff.ts +33 -0
- package/src/core/objects/aggregate/aggregate.model.ts +22 -1
- package/src/core/objects/aggregate/changes/aggregate.base.ts +5 -1
- package/src/core/objects/aggregate/changes/aggregate.security-label.ts +99 -0
- package/src/core/objects/aggregate/changes/aggregate.types.ts +3 -1
- package/src/core/objects/base.model.ts +2 -0
- package/src/core/objects/domain/changes/domain.base.ts +5 -1
- package/src/core/objects/domain/changes/domain.security-label.test.ts +56 -0
- package/src/core/objects/domain/changes/domain.security-label.ts +77 -0
- package/src/core/objects/domain/changes/domain.types.ts +3 -1
- package/src/core/objects/domain/domain.diff.ts +33 -0
- package/src/core/objects/domain/domain.model.ts +22 -1
- package/src/core/objects/event-trigger/changes/event-trigger.base.ts +1 -1
- package/src/core/objects/event-trigger/changes/event-trigger.security-label.ts +95 -0
- package/src/core/objects/event-trigger/changes/event-trigger.types.ts +3 -1
- package/src/core/objects/event-trigger/event-trigger.diff.ts +33 -0
- package/src/core/objects/event-trigger/event-trigger.model.ts +22 -1
- package/src/core/objects/extract-with-retry.test.ts +143 -0
- package/src/core/objects/extract-with-retry.ts +87 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.base.ts +5 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.security-label.ts +95 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +3 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +33 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +24 -1
- package/src/core/objects/index/index.diff.ts +0 -1
- package/src/core/objects/index/index.model.test.ts +37 -1
- package/src/core/objects/index/index.model.ts +25 -6
- package/src/core/objects/materialized-view/changes/materialized-view.base.ts +5 -1
- package/src/core/objects/materialized-view/changes/materialized-view.security-label.test.ts +63 -0
- package/src/core/objects/materialized-view/changes/materialized-view.security-label.ts +95 -0
- package/src/core/objects/materialized-view/changes/materialized-view.types.ts +3 -1
- package/src/core/objects/materialized-view/materialized-view.diff.ts +37 -0
- package/src/core/objects/materialized-view/materialized-view.model.test.ts +93 -0
- package/src/core/objects/materialized-view/materialized-view.model.ts +52 -8
- package/src/core/objects/procedure/changes/procedure.base.ts +5 -1
- package/src/core/objects/procedure/changes/procedure.security-label.ts +105 -0
- package/src/core/objects/procedure/changes/procedure.types.ts +3 -1
- package/src/core/objects/procedure/procedure.diff.ts +33 -0
- package/src/core/objects/procedure/procedure.model.test.ts +117 -0
- package/src/core/objects/procedure/procedure.model.ts +51 -7
- package/src/core/objects/publication/changes/publication.base.ts +1 -1
- package/src/core/objects/publication/changes/publication.security-label.ts +95 -0
- package/src/core/objects/publication/changes/publication.types.ts +3 -1
- package/src/core/objects/publication/publication.diff.ts +33 -0
- package/src/core/objects/publication/publication.model.ts +24 -1
- package/src/core/objects/rls-policy/rls-policy.diff.ts +19 -1
- package/src/core/objects/role/changes/role.base.ts +2 -1
- package/src/core/objects/role/changes/role.security-label.ts +77 -0
- package/src/core/objects/role/changes/role.types.ts +3 -1
- package/src/core/objects/role/role.diff.ts +33 -0
- package/src/core/objects/role/role.model.ts +32 -0
- package/src/core/objects/rule/rule.model.test.ts +99 -0
- package/src/core/objects/rule/rule.model.ts +28 -4
- package/src/core/objects/schema/changes/schema.alter.test.ts +1 -0
- package/src/core/objects/schema/changes/schema.base.ts +5 -1
- package/src/core/objects/schema/changes/schema.create.test.ts +1 -0
- package/src/core/objects/schema/changes/schema.drop.test.ts +1 -0
- package/src/core/objects/schema/changes/schema.security-label.test.ts +76 -0
- package/src/core/objects/schema/changes/schema.security-label.ts +77 -0
- package/src/core/objects/schema/changes/schema.types.ts +3 -1
- package/src/core/objects/schema/schema.diff.test.ts +1 -0
- package/src/core/objects/schema/schema.diff.ts +43 -1
- package/src/core/objects/schema/schema.model.ts +21 -1
- package/src/core/objects/security-label.types.test.ts +106 -0
- package/src/core/objects/security-label.types.ts +61 -0
- package/src/core/objects/sequence/changes/sequence.base.ts +5 -1
- package/src/core/objects/sequence/changes/sequence.security-label.test.ts +58 -0
- package/src/core/objects/sequence/changes/sequence.security-label.ts +92 -0
- package/src/core/objects/sequence/changes/sequence.types.ts +3 -1
- package/src/core/objects/sequence/sequence.diff.test.ts +87 -0
- package/src/core/objects/sequence/sequence.diff.ts +64 -6
- package/src/core/objects/sequence/sequence.model.ts +22 -1
- package/src/core/objects/subscription/changes/subscription.base.ts +1 -1
- package/src/core/objects/subscription/changes/subscription.security-label.ts +95 -0
- package/src/core/objects/subscription/changes/subscription.types.ts +3 -1
- package/src/core/objects/subscription/subscription.diff.ts +33 -0
- package/src/core/objects/subscription/subscription.model.ts +22 -1
- package/src/core/objects/table/changes/table.alter.test.ts +13 -21
- package/src/core/objects/table/changes/table.alter.ts +30 -3
- package/src/core/objects/table/changes/table.base.ts +5 -1
- package/src/core/objects/table/changes/table.security-label.test.ts +140 -0
- package/src/core/objects/table/changes/table.security-label.ts +183 -0
- package/src/core/objects/table/changes/table.types.ts +3 -1
- package/src/core/objects/table/table.diff.ts +111 -19
- package/src/core/objects/table/table.model.test.ts +209 -0
- package/src/core/objects/table/table.model.ts +94 -9
- package/src/core/objects/trigger/trigger.model.test.ts +113 -0
- package/src/core/objects/trigger/trigger.model.ts +28 -5
- package/src/core/objects/type/composite-type/changes/composite-type.base.ts +5 -1
- package/src/core/objects/type/composite-type/changes/composite-type.security-label.ts +95 -0
- package/src/core/objects/type/composite-type/changes/composite-type.types.ts +3 -1
- package/src/core/objects/type/composite-type/composite-type.diff.ts +33 -0
- package/src/core/objects/type/composite-type/composite-type.model.ts +26 -2
- package/src/core/objects/type/enum/changes/enum.base.ts +5 -1
- package/src/core/objects/type/enum/changes/enum.security-label.ts +77 -0
- package/src/core/objects/type/enum/changes/enum.types.ts +3 -1
- package/src/core/objects/type/enum/enum.diff.ts +33 -0
- package/src/core/objects/type/enum/enum.model.ts +25 -1
- package/src/core/objects/type/range/changes/range.base.ts +5 -1
- package/src/core/objects/type/range/changes/range.security-label.ts +77 -0
- package/src/core/objects/type/range/changes/range.types.ts +3 -1
- package/src/core/objects/type/range/range.diff.ts +33 -0
- package/src/core/objects/type/range/range.model.ts +22 -1
- package/src/core/objects/utils.ts +6 -0
- package/src/core/objects/view/changes/view.base.ts +5 -1
- package/src/core/objects/view/changes/view.security-label.test.ts +64 -0
- package/src/core/objects/view/changes/view.security-label.ts +77 -0
- package/src/core/objects/view/changes/view.types.ts +3 -1
- package/src/core/objects/view/view.diff.ts +31 -0
- package/src/core/objects/view/view.model.test.ts +90 -0
- package/src/core/objects/view/view.model.ts +53 -7
- package/src/core/plan/create.ts +3 -1
- package/src/core/plan/sql-format/fixtures.ts +1 -0
- package/src/core/plan/types.ts +8 -0
- package/src/core/{post-diff-cycle-breaking.test.ts → post-diff-normalization.test.ts} +168 -4
- package/src/core/post-diff-normalization.ts +260 -0
- package/src/core/sort/cycle-breakers.ts +1 -1
- package/src/core/sort/utils.ts +38 -0
- package/dist/core/post-diff-cycle-breaking.js +0 -100
- package/src/core/post-diff-cycle-breaking.ts +0 -138
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { CreateIndex } from "./objects/index/changes/index.create.js";
|
|
2
|
+
import { DropIndex } from "./objects/index/changes/index.drop.js";
|
|
3
|
+
import { AlterTableAddConstraint, AlterTableDropColumn, AlterTableDropConstraint, AlterTableSetReplicaIdentity, AlterTableValidateConstraint, } from "./objects/table/changes/table.alter.js";
|
|
4
|
+
import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.js";
|
|
5
|
+
import { stableId } from "./objects/utils.js";
|
|
6
|
+
function constraintStableId(table, constraintName) {
|
|
7
|
+
return stableId.constraint(table.schema, table.name, constraintName);
|
|
8
|
+
}
|
|
9
|
+
function isSupersededByTableReplacement(change, replacedTableIds) {
|
|
10
|
+
if (!(change instanceof AlterTableDropColumn) &&
|
|
11
|
+
!(change instanceof AlterTableDropConstraint)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return replacedTableIds.has(change.table.stableId);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Drop earlier duplicates of `AlterTableAddConstraint` /
|
|
18
|
+
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` targeting
|
|
19
|
+
* replaced tables, keeping only the last occurrence of each
|
|
20
|
+
* `(changeType, table.stableId, constraint.name)`.
|
|
21
|
+
*
|
|
22
|
+
* When `expandReplaceDependencies()` promotes a table to a full
|
|
23
|
+
* `DropTable + CreateTable` pair, it also emits one
|
|
24
|
+
* `AlterTableAddConstraint` (plus optional `VALIDATE CONSTRAINT` /
|
|
25
|
+
* `COMMENT ON CONSTRAINT`) per branch constraint. If `diffTables()` already
|
|
26
|
+
* emitted the same change for a shape flip or a new constraint on that
|
|
27
|
+
* table, the plan ends up with two identical `ALTER TABLE ... ADD
|
|
28
|
+
* CONSTRAINT ...` statements and PostgreSQL fails at apply time with
|
|
29
|
+
* `constraint "..." for relation "..." already exists`. Because
|
|
30
|
+
* `expandReplaceDependencies()` appends its additions after the original
|
|
31
|
+
* `diffTables()` output, the last occurrence is the expansion's emission —
|
|
32
|
+
* keeping it preserves correctness while removing the duplicate.
|
|
33
|
+
*/
|
|
34
|
+
function dropReplacedTableDuplicateConstraintChanges(changes, replacedTableIds) {
|
|
35
|
+
if (replacedTableIds.size === 0)
|
|
36
|
+
return changes;
|
|
37
|
+
const keyFor = (change) => {
|
|
38
|
+
if (!(change instanceof AlterTableAddConstraint) &&
|
|
39
|
+
!(change instanceof AlterTableValidateConstraint) &&
|
|
40
|
+
!(change instanceof CreateCommentOnConstraint)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!replacedTableIds.has(change.table.stableId))
|
|
44
|
+
return null;
|
|
45
|
+
const tag = change instanceof AlterTableAddConstraint
|
|
46
|
+
? "add"
|
|
47
|
+
: change instanceof AlterTableValidateConstraint
|
|
48
|
+
? "validate"
|
|
49
|
+
: "comment";
|
|
50
|
+
return `${tag}:${constraintStableId(change.table, change.constraint.name)}`;
|
|
51
|
+
};
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
const reversedKept = [];
|
|
54
|
+
let mutated = false;
|
|
55
|
+
// Walk backwards: the first encounter of each key corresponds to its LAST
|
|
56
|
+
// occurrence in the original order. `expandReplaceDependencies()` appends
|
|
57
|
+
// additions after the original changes, so "last wins" keeps the
|
|
58
|
+
// expansion's emission and drops the earlier diffTables duplicate.
|
|
59
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
60
|
+
const change = changes[i];
|
|
61
|
+
const key = keyFor(change);
|
|
62
|
+
if (key !== null) {
|
|
63
|
+
if (seen.has(key)) {
|
|
64
|
+
mutated = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
seen.add(key);
|
|
68
|
+
}
|
|
69
|
+
reversedKept.push(change);
|
|
70
|
+
}
|
|
71
|
+
return mutated ? reversedKept.reverse() : changes;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Re-emit `ALTER TABLE ... REPLICA IDENTITY USING INDEX <idx>` after any
|
|
75
|
+
* `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica-identity
|
|
76
|
+
* index of a branch table.
|
|
77
|
+
*
|
|
78
|
+
* Background: PostgreSQL silently flips a table's `relreplident` to `'d'`
|
|
79
|
+
* (DEFAULT) when the index it points to is dropped. `CREATE INDEX` cannot
|
|
80
|
+
* restore the marker — only `ALTER TABLE ... REPLICA IDENTITY USING INDEX`
|
|
81
|
+
* can. When both main and branch carry `replica_identity = 'i'` pointing at
|
|
82
|
+
* the same index name, `diffTables()` emits no replica-identity change of its
|
|
83
|
+
* own, so the marker would be lost on apply.
|
|
84
|
+
*
|
|
85
|
+
* This is a whole-plan interaction: `diffTables()` cannot detect it without
|
|
86
|
+
* also looking at index changes. Per the "whole-plan interactions belong in
|
|
87
|
+
* post-diff normalization" rule in the package CLAUDE.md, the restoration
|
|
88
|
+
* lives here.
|
|
89
|
+
*
|
|
90
|
+
* Insertion is idempotent: if `diffTables()` already emitted the same
|
|
91
|
+
* `AlterTableSetReplicaIdentity` for this table (e.g. when the user is also
|
|
92
|
+
* switching the replica-identity index name in the same migration), no
|
|
93
|
+
* duplicate is added.
|
|
94
|
+
*/
|
|
95
|
+
function restoreReplicaIdentityAfterIndexReplace(changes, branchTables) {
|
|
96
|
+
// Build the index-stable-id → owning-table map from branch state. Only
|
|
97
|
+
// tables in 'i' mode contribute, and only those whose configured index name
|
|
98
|
+
// is non-null (the extractor returns null for any other mode).
|
|
99
|
+
const replicaIdentityIndexToTable = new Map();
|
|
100
|
+
for (const table of Object.values(branchTables)) {
|
|
101
|
+
if (table.replica_identity !== "i" || !table.replica_identity_index) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const indexId = stableId.index(table.schema, table.name, table.replica_identity_index);
|
|
105
|
+
replicaIdentityIndexToTable.set(indexId, table);
|
|
106
|
+
}
|
|
107
|
+
if (replicaIdentityIndexToTable.size === 0)
|
|
108
|
+
return changes;
|
|
109
|
+
// Find the indexes that are both dropped AND created in this plan. A pure
|
|
110
|
+
// drop or a pure create is handled by `diffTables()` directly (the table's
|
|
111
|
+
// replica_identity / replica_identity_index fields will have changed). The
|
|
112
|
+
// hole is specifically the drop+create pair that recreates the same name.
|
|
113
|
+
const droppedIndexIds = new Set();
|
|
114
|
+
const createdIndexIds = new Set();
|
|
115
|
+
for (const change of changes) {
|
|
116
|
+
if (change instanceof DropIndex) {
|
|
117
|
+
droppedIndexIds.add(change.index.stableId);
|
|
118
|
+
}
|
|
119
|
+
else if (change instanceof CreateIndex) {
|
|
120
|
+
createdIndexIds.add(change.index.stableId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const replacedIndexIds = new Set();
|
|
124
|
+
for (const id of droppedIndexIds) {
|
|
125
|
+
if (createdIndexIds.has(id) && replicaIdentityIndexToTable.has(id)) {
|
|
126
|
+
replacedIndexIds.add(id);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (replacedIndexIds.size === 0)
|
|
130
|
+
return changes;
|
|
131
|
+
// Skip tables for which `diffTables()` already emitted a replica-identity
|
|
132
|
+
// setter — re-emitting would produce a redundant ALTER TABLE (harmless on
|
|
133
|
+
// apply, but noisy in plan output).
|
|
134
|
+
const tablesWithExistingReplicaIdentitySetter = new Set();
|
|
135
|
+
for (const change of changes) {
|
|
136
|
+
if (change instanceof AlterTableSetReplicaIdentity) {
|
|
137
|
+
tablesWithExistingReplicaIdentitySetter.add(change.table.stableId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Insert one `AlterTableSetReplicaIdentity` per replaced index, immediately
|
|
141
|
+
// after the matching `CreateIndex`. The change's `requires` already names
|
|
142
|
+
// both the table and the recreated index, so the topo sort orders it
|
|
143
|
+
// correctly relative to the surrounding DDL.
|
|
144
|
+
const result = [];
|
|
145
|
+
for (const change of changes) {
|
|
146
|
+
result.push(change);
|
|
147
|
+
if (!(change instanceof CreateIndex) ||
|
|
148
|
+
!replacedIndexIds.has(change.index.stableId)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const table = replicaIdentityIndexToTable.get(change.index.stableId);
|
|
152
|
+
if (!table)
|
|
153
|
+
continue;
|
|
154
|
+
if (tablesWithExistingReplicaIdentitySetter.has(table.stableId))
|
|
155
|
+
continue;
|
|
156
|
+
result.push(new AlterTableSetReplicaIdentity({
|
|
157
|
+
table,
|
|
158
|
+
mode: "i",
|
|
159
|
+
indexName: table.replica_identity_index,
|
|
160
|
+
}));
|
|
161
|
+
// Mark as emitted so a second replaced index on the same table — if that
|
|
162
|
+
// ever arises — doesn't double-emit.
|
|
163
|
+
tablesWithExistingReplicaIdentitySetter.add(table.stableId);
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Apply structural rewrites to the change list that are only obvious once
|
|
169
|
+
* every object diff has been collected. This pass does NOT prevent dependency
|
|
170
|
+
* cycles — that responsibility now lives in the sort phase, where
|
|
171
|
+
* `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
|
|
172
|
+
* that edge filtering can't break (FK SCC of dropped tables,
|
|
173
|
+
* AlterPublicationDropTables ↔ AlterTableDropColumn, …).
|
|
174
|
+
*
|
|
175
|
+
* Concretely, this pass:
|
|
176
|
+
*
|
|
177
|
+
* - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
|
|
178
|
+
* changes that are made redundant by an expansion-emitted
|
|
179
|
+
* `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
|
|
180
|
+
* would try to drop a column that no longer exists in the freshly
|
|
181
|
+
* recreated table.
|
|
182
|
+
* - Dedupes duplicate `AlterTableAddConstraint` /
|
|
183
|
+
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
|
|
184
|
+
* produced when `diffTables()` and `expandReplaceDependencies()` both
|
|
185
|
+
* emit the same constraint operation for a replaced table. Last write
|
|
186
|
+
* wins so the expansion's emission survives.
|
|
187
|
+
* - Re-emits `ALTER TABLE ... REPLICA IDENTITY USING INDEX <idx>` after any
|
|
188
|
+
* `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica
|
|
189
|
+
* identity index of a branch table — Postgres silently clears the marker
|
|
190
|
+
* when the underlying index is dropped, and `CREATE INDEX` cannot restore
|
|
191
|
+
* it.
|
|
192
|
+
*
|
|
193
|
+
* Object-local PostgreSQL semantics (for example owned-sequence cascades)
|
|
194
|
+
* stay in the corresponding `diff*` function instead of this pass.
|
|
195
|
+
*/
|
|
196
|
+
export function normalizePostDiffChanges({ changes, replacedTableIds = new Set(), branchTables = {}, }) {
|
|
197
|
+
const restoredChanges = restoreReplicaIdentityAfterIndexReplace(changes, branchTables);
|
|
198
|
+
const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(restoredChanges, replacedTableIds);
|
|
199
|
+
if (replacedTableIds.size === 0)
|
|
200
|
+
return dedupedChanges;
|
|
201
|
+
return dedupedChanges.filter((change) => !isSupersededByTableReplacement(change, replacedTableIds));
|
|
202
|
+
}
|
|
@@ -245,7 +245,7 @@ function tryBreakPublicationColumnCycle(cycleNodeIndexes, phaseChanges) {
|
|
|
245
245
|
return null;
|
|
246
246
|
// Verify the table is NOT itself being dropped. If `DropTable(T)` is in
|
|
247
247
|
// the same phase, the existing structural rewrites in
|
|
248
|
-
// `post-diff-
|
|
248
|
+
// `post-diff-normalization.ts` (replace-expansion superseded filter)
|
|
249
249
|
// already prune the redundant `AlterTableDropColumn`, so we should not
|
|
250
250
|
// see this combination here. Be defensive and bail anyway — flipping
|
|
251
251
|
// `omitTableRequirement` when T is being dropped would let the column
|
|
@@ -19,5 +19,15 @@ export declare function isMetadataStableId(stableId: string): boolean;
|
|
|
19
19
|
* - ALTER operations with scope="privilege" → create_alter_object phase (metadata changes)
|
|
20
20
|
* - ALTER operations that drop actual objects → drop phase (destructive ALTER)
|
|
21
21
|
* - ALTER operations that don't drop objects → create_alter_object phase (non-destructive ALTER)
|
|
22
|
+
*
|
|
23
|
+
* Dependency-breaking ALTERs that remove a `pg_depend` edge to another
|
|
24
|
+
* object that may be dropped in the same plan (for example
|
|
25
|
+
* `ALTER COLUMN ... DROP DEFAULT` releasing a sequence reference, or
|
|
26
|
+
* `ALTER COLUMN ... TYPE <built-in>` releasing a user-defined type
|
|
27
|
+
* reference) are routed to the drop phase. The drop phase sorts in reverse
|
|
28
|
+
* dependency order using the main catalog, so the catalog edges already
|
|
29
|
+
* in `pg_depend` order the ALTER before any dependent `DROP TYPE` /
|
|
30
|
+
* `DROP SEQUENCE` / `DROP FUNCTION` and PostgreSQL no longer rejects the
|
|
31
|
+
* drop with error 2BP01.
|
|
22
32
|
*/
|
|
23
33
|
export declare function getExecutionPhase(change: Change): Phase;
|
package/dist/core/sort/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AlterTableAlterColumnDropDefault, AlterTableAlterColumnDropIdentity, AlterTableAlterColumnType, } from "../objects/table/changes/table.alter.js";
|
|
1
2
|
/**
|
|
2
3
|
* Check if a stable ID represents metadata (ACL, default privileges, comments, etc.)
|
|
3
4
|
* rather than an actual database object.
|
|
@@ -20,6 +21,16 @@ export function isMetadataStableId(stableId) {
|
|
|
20
21
|
* - ALTER operations with scope="privilege" → create_alter_object phase (metadata changes)
|
|
21
22
|
* - ALTER operations that drop actual objects → drop phase (destructive ALTER)
|
|
22
23
|
* - ALTER operations that don't drop objects → create_alter_object phase (non-destructive ALTER)
|
|
24
|
+
*
|
|
25
|
+
* Dependency-breaking ALTERs that remove a `pg_depend` edge to another
|
|
26
|
+
* object that may be dropped in the same plan (for example
|
|
27
|
+
* `ALTER COLUMN ... DROP DEFAULT` releasing a sequence reference, or
|
|
28
|
+
* `ALTER COLUMN ... TYPE <built-in>` releasing a user-defined type
|
|
29
|
+
* reference) are routed to the drop phase. The drop phase sorts in reverse
|
|
30
|
+
* dependency order using the main catalog, so the catalog edges already
|
|
31
|
+
* in `pg_depend` order the ALTER before any dependent `DROP TYPE` /
|
|
32
|
+
* `DROP SEQUENCE` / `DROP FUNCTION` and PostgreSQL no longer rejects the
|
|
33
|
+
* drop with error 2BP01.
|
|
23
34
|
*/
|
|
24
35
|
export function getExecutionPhase(change) {
|
|
25
36
|
// DROP operations always go to drop phase
|
|
@@ -43,6 +54,23 @@ export function getExecutionPhase(change) {
|
|
|
43
54
|
// Destructive ALTER (DROP COLUMN, DROP CONSTRAINT, etc.) → drop phase
|
|
44
55
|
return "drop";
|
|
45
56
|
}
|
|
57
|
+
// Dependency-breaking column ALTERs that release a pg_depend edge.
|
|
58
|
+
// Routing these to the drop phase lets the existing catalog dependency
|
|
59
|
+
// edges (column → sequence, column → identity sequence) order them
|
|
60
|
+
// before the matching DROP statement.
|
|
61
|
+
if (change instanceof AlterTableAlterColumnDropDefault ||
|
|
62
|
+
change instanceof AlterTableAlterColumnDropIdentity) {
|
|
63
|
+
return "drop";
|
|
64
|
+
}
|
|
65
|
+
// ALTER COLUMN ... TYPE only safely runs in the drop phase when the
|
|
66
|
+
// target type is built-in. For user-defined target types we cannot tell
|
|
67
|
+
// here whether the type is created in the same plan, and the create
|
|
68
|
+
// happens in create_alter phase, so we keep the alter in that phase to
|
|
69
|
+
// preserve the create-then-alter ordering.
|
|
70
|
+
if (change instanceof AlterTableAlterColumnType &&
|
|
71
|
+
!change.column.is_custom_type) {
|
|
72
|
+
return "drop";
|
|
73
|
+
}
|
|
46
74
|
// Non-destructive ALTER (ADD COLUMN, GRANT, etc.) → create_alter phase
|
|
47
75
|
return "create_alter_object";
|
|
48
76
|
}
|
package/package.json
CHANGED
package/src/core/catalog.diff.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
2
|
import type { Catalog } from "./catalog.model.ts";
|
|
3
3
|
import { expandReplaceDependencies } from "./expand-replace-dependencies.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { normalizePostDiffChanges } from "./post-diff-normalization.ts";
|
|
5
5
|
|
|
6
6
|
const debugCatalog = debug("pg-delta:catalog");
|
|
7
7
|
|
|
@@ -190,6 +190,7 @@ export function diffCatalogs(
|
|
|
190
190
|
main.sequences,
|
|
191
191
|
branch.sequences,
|
|
192
192
|
branch.tables,
|
|
193
|
+
main.tables,
|
|
193
194
|
),
|
|
194
195
|
);
|
|
195
196
|
changes.push(...diffTables(diffContext, main.tables, branch.tables));
|
|
@@ -238,9 +239,10 @@ export function diffCatalogs(
|
|
|
238
239
|
mainCatalog: main,
|
|
239
240
|
branchCatalog: branch,
|
|
240
241
|
});
|
|
241
|
-
filteredChanges =
|
|
242
|
+
filteredChanges = normalizePostDiffChanges({
|
|
242
243
|
changes: expandedDependencies.changes,
|
|
243
244
|
replacedTableIds: expandedDependencies.replacedTableIds,
|
|
245
|
+
branchTables: branch.tables,
|
|
244
246
|
});
|
|
245
247
|
|
|
246
248
|
debugCatalog(
|
|
@@ -267,6 +267,7 @@ export async function createEmptyCatalog(
|
|
|
267
267
|
owner: currentUser,
|
|
268
268
|
comment: "standard public schema",
|
|
269
269
|
privileges: [],
|
|
270
|
+
security_labels: [],
|
|
270
271
|
});
|
|
271
272
|
|
|
272
273
|
return new Catalog({
|
|
@@ -302,7 +303,19 @@ export async function createEmptyCatalog(
|
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
305
|
|
|
305
|
-
|
|
306
|
+
interface ExtractCatalogOptions {
|
|
307
|
+
/**
|
|
308
|
+
* Number of retry attempts for catalog extractors when `pg_get_*def()`
|
|
309
|
+
* returns NULL for at least one row. See `ExtractRetryOptions.retries`.
|
|
310
|
+
*/
|
|
311
|
+
extractRetries?: number;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export async function extractCatalog(
|
|
315
|
+
pool: Pool,
|
|
316
|
+
options: ExtractCatalogOptions = {},
|
|
317
|
+
) {
|
|
318
|
+
const retryOptions = { retries: options.extractRetries };
|
|
306
319
|
const [
|
|
307
320
|
aggregates,
|
|
308
321
|
collations,
|
|
@@ -339,21 +352,21 @@ export async function extractCatalog(pool: Pool) {
|
|
|
339
352
|
extractDomains(pool).then(listToRecord),
|
|
340
353
|
extractEnums(pool).then(listToRecord),
|
|
341
354
|
extractExtensions(pool).then(listToRecord),
|
|
342
|
-
extractIndexes(pool).then(listToRecord),
|
|
343
|
-
extractMaterializedViews(pool).then(listToRecord),
|
|
355
|
+
extractIndexes(pool, retryOptions).then(listToRecord),
|
|
356
|
+
extractMaterializedViews(pool, retryOptions).then(listToRecord),
|
|
344
357
|
extractSubscriptions(pool).then(listToRecord),
|
|
345
358
|
extractPublications(pool).then(listToRecord),
|
|
346
|
-
extractProcedures(pool).then(listToRecord),
|
|
359
|
+
extractProcedures(pool, retryOptions).then(listToRecord),
|
|
347
360
|
extractRlsPolicies(pool).then(listToRecord),
|
|
348
361
|
extractRoles(pool).then(listToRecord),
|
|
349
362
|
extractSchemas(pool).then(listToRecord),
|
|
350
363
|
extractSequences(pool).then(listToRecord),
|
|
351
|
-
extractTables(pool).then(listToRecord),
|
|
352
|
-
extractTriggers(pool).then(listToRecord),
|
|
364
|
+
extractTables(pool, retryOptions).then(listToRecord),
|
|
365
|
+
extractTriggers(pool, retryOptions).then(listToRecord),
|
|
353
366
|
extractEventTriggers(pool).then(listToRecord),
|
|
354
|
-
extractRules(pool).then(listToRecord),
|
|
367
|
+
extractRules(pool, retryOptions).then(listToRecord),
|
|
355
368
|
extractRanges(pool).then(listToRecord),
|
|
356
|
-
extractViews(pool).then(listToRecord),
|
|
369
|
+
extractViews(pool, retryOptions).then(listToRecord),
|
|
357
370
|
extractForeignDataWrappers(pool).then(listToRecord),
|
|
358
371
|
extractServers(pool).then(listToRecord),
|
|
359
372
|
extractUserMappings(pool).then(listToRecord),
|
|
@@ -97,6 +97,137 @@ describe("expandReplaceDependencies", () => {
|
|
|
97
97
|
expect(result.replacedTableIds.size).toBe(0);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
test("promotes surviving dependent view when its referenced table is dropped without a same-name create", async () => {
|
|
101
|
+
// Reproduces issue #228 case 3: ALTER TABLE users RENAME TO members.
|
|
102
|
+
// pg-delta sees `users` as drop-only and `members` as create-only — the
|
|
103
|
+
// stableIds differ, so neither is in the createdIds∩droppedIds replace
|
|
104
|
+
// root set. The dependent view `user_count` exists in both catalogs
|
|
105
|
+
// (its definition was rewritten to FROM members in branch). Without
|
|
106
|
+
// expansion, DROP TABLE users would fail because user_count still
|
|
107
|
+
// references it. The expander must seed the drop-only table as a root
|
|
108
|
+
// so the surviving dependent gets promoted to DROP+CREATE.
|
|
109
|
+
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
110
|
+
const usersTable = new Table({
|
|
111
|
+
schema: "public",
|
|
112
|
+
name: "users",
|
|
113
|
+
persistence: "p",
|
|
114
|
+
row_security: false,
|
|
115
|
+
force_row_security: false,
|
|
116
|
+
has_indexes: false,
|
|
117
|
+
has_rules: false,
|
|
118
|
+
has_triggers: false,
|
|
119
|
+
has_subclasses: false,
|
|
120
|
+
is_populated: true,
|
|
121
|
+
replica_identity: "d",
|
|
122
|
+
is_partition: false,
|
|
123
|
+
options: null,
|
|
124
|
+
partition_bound: null,
|
|
125
|
+
partition_by: null,
|
|
126
|
+
owner: "postgres",
|
|
127
|
+
comment: null,
|
|
128
|
+
parent_schema: null,
|
|
129
|
+
parent_name: null,
|
|
130
|
+
columns: [
|
|
131
|
+
{
|
|
132
|
+
name: "id",
|
|
133
|
+
position: 1,
|
|
134
|
+
data_type: "integer",
|
|
135
|
+
data_type_str: "integer",
|
|
136
|
+
is_custom_type: false,
|
|
137
|
+
custom_type_type: null,
|
|
138
|
+
custom_type_category: null,
|
|
139
|
+
custom_type_schema: null,
|
|
140
|
+
custom_type_name: null,
|
|
141
|
+
not_null: true,
|
|
142
|
+
is_identity: false,
|
|
143
|
+
is_identity_always: false,
|
|
144
|
+
is_generated: false,
|
|
145
|
+
collation: null,
|
|
146
|
+
default: null,
|
|
147
|
+
comment: null,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
privileges: [],
|
|
151
|
+
});
|
|
152
|
+
const mainView = new View({
|
|
153
|
+
schema: "public",
|
|
154
|
+
name: "user_count",
|
|
155
|
+
owner: "postgres",
|
|
156
|
+
definition: " SELECT count(*) AS n FROM public.users;",
|
|
157
|
+
row_security: false,
|
|
158
|
+
force_row_security: false,
|
|
159
|
+
has_indexes: false,
|
|
160
|
+
has_rules: false,
|
|
161
|
+
has_triggers: false,
|
|
162
|
+
has_subclasses: false,
|
|
163
|
+
is_populated: true,
|
|
164
|
+
replica_identity: "d",
|
|
165
|
+
is_partition: false,
|
|
166
|
+
partition_bound: null,
|
|
167
|
+
comment: null,
|
|
168
|
+
columns: [
|
|
169
|
+
{
|
|
170
|
+
name: "n",
|
|
171
|
+
position: 1,
|
|
172
|
+
data_type: "bigint",
|
|
173
|
+
data_type_str: "bigint",
|
|
174
|
+
is_custom_type: false,
|
|
175
|
+
custom_type_type: null,
|
|
176
|
+
custom_type_category: null,
|
|
177
|
+
custom_type_schema: null,
|
|
178
|
+
custom_type_name: null,
|
|
179
|
+
not_null: false,
|
|
180
|
+
is_identity: false,
|
|
181
|
+
is_identity_always: false,
|
|
182
|
+
is_generated: false,
|
|
183
|
+
collation: null,
|
|
184
|
+
default: null,
|
|
185
|
+
comment: null,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
options: null,
|
|
189
|
+
privileges: [],
|
|
190
|
+
});
|
|
191
|
+
const branchView = new View({
|
|
192
|
+
...mainView,
|
|
193
|
+
definition: " SELECT count(*) AS n FROM public.members;",
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const mainCatalog = new Catalog({
|
|
197
|
+
...baseline,
|
|
198
|
+
tables: { [usersTable.stableId]: usersTable },
|
|
199
|
+
views: { [mainView.stableId]: mainView },
|
|
200
|
+
depends: [
|
|
201
|
+
{
|
|
202
|
+
dependent_stable_id: mainView.stableId,
|
|
203
|
+
referenced_stable_id: usersTable.stableId,
|
|
204
|
+
deptype: "n",
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
const branchCatalog = new Catalog({
|
|
209
|
+
...baseline,
|
|
210
|
+
views: { [branchView.stableId]: branchView },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Simulated planner output: DropTable(users) + CreateView orReplace(user_count).
|
|
214
|
+
// The surviving view appears only as a "create" (CREATE OR REPLACE VIEW),
|
|
215
|
+
// never as a drop, so DROP TABLE users would fail without expansion.
|
|
216
|
+
const changes: Change[] = [
|
|
217
|
+
new DropTable({ table: usersTable }),
|
|
218
|
+
new CreateView({ view: branchView, orReplace: true }),
|
|
219
|
+
];
|
|
220
|
+
const result = expandReplaceDependencies({
|
|
221
|
+
changes,
|
|
222
|
+
mainCatalog,
|
|
223
|
+
branchCatalog,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// The view's surviving CREATE OR REPLACE remains, AND a DropView is
|
|
227
|
+
// injected so the drop phase removes the view before the table.
|
|
228
|
+
expect(result.changes.some((c) => c instanceof DropView)).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
100
231
|
test("does not replace the owning table for an owned sequence recreation", async () => {
|
|
101
232
|
const baseline = await createEmptyCatalog(170000, "postgres");
|
|
102
233
|
// Use `persistence` (UNLOGGED → LOGGED) to trigger the
|
|
@@ -129,6 +129,30 @@ export function expandReplaceDependencies({
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// Drop-only objects (no matching create — typically a renamed-away table or
|
|
133
|
+
// type) are also expansion roots: anything in main that depends on them via
|
|
134
|
+
// pg_depend must drop before the parent does. Without this seed, a renamed
|
|
135
|
+
// table whose dependent view stays in the branch catalog (with an updated
|
|
136
|
+
// definition that no longer references the old name) would still try to
|
|
137
|
+
// run DROP TABLE old_name while old_name is referenced by the view, which
|
|
138
|
+
// PostgreSQL refuses without CASCADE. The walk below promotes the surviving
|
|
139
|
+
// dependent to DROP+CREATE so its drop is sequenced before the parent drop.
|
|
140
|
+
for (const id of droppedIds) {
|
|
141
|
+
if (createdIds.has(id)) continue;
|
|
142
|
+
if (replaceRoots.has(id)) continue;
|
|
143
|
+
// Only seed for object kinds that can have catalog dependents we know
|
|
144
|
+
// how to recreate via buildReplaceChanges.
|
|
145
|
+
if (
|
|
146
|
+
id.startsWith("table:") ||
|
|
147
|
+
id.startsWith("view:") ||
|
|
148
|
+
id.startsWith("materializedView:") ||
|
|
149
|
+
id.startsWith("type:") ||
|
|
150
|
+
id.startsWith("domain:")
|
|
151
|
+
) {
|
|
152
|
+
replaceRoots.add(id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
if (replaceRoots.size === 0) {
|
|
133
157
|
return {
|
|
134
158
|
changes,
|
|
@@ -41,6 +41,16 @@ const membershipChange = {
|
|
|
41
41
|
requires: [],
|
|
42
42
|
} as unknown as Change;
|
|
43
43
|
|
|
44
|
+
const securityLabelChange = {
|
|
45
|
+
objectType: "schema",
|
|
46
|
+
operation: "create",
|
|
47
|
+
scope: "security_label",
|
|
48
|
+
schema: { name: "labeled" },
|
|
49
|
+
securityLabel: { provider: "dummy", label: "classified" },
|
|
50
|
+
requires: ["schema:labeled"],
|
|
51
|
+
creates: ["security_label:schema:labeled:dummy"],
|
|
52
|
+
} as unknown as Change;
|
|
53
|
+
|
|
44
54
|
describe("evaluatePattern", () => {
|
|
45
55
|
describe("bare key matching (top-level properties)", () => {
|
|
46
56
|
test("objectType match", () => {
|
|
@@ -242,6 +252,23 @@ describe("evaluatePattern", () => {
|
|
|
242
252
|
});
|
|
243
253
|
});
|
|
244
254
|
|
|
255
|
+
describe("security label matching", () => {
|
|
256
|
+
test("provider matches security label changes", () => {
|
|
257
|
+
expect(
|
|
258
|
+
evaluatePattern(
|
|
259
|
+
{ scope: "security_label", provider: "dummy" },
|
|
260
|
+
securityLabelChange,
|
|
261
|
+
),
|
|
262
|
+
).toBe(true);
|
|
263
|
+
expect(
|
|
264
|
+
evaluatePattern(
|
|
265
|
+
{ scope: "security_label", provider: "other" },
|
|
266
|
+
securityLabelChange,
|
|
267
|
+
),
|
|
268
|
+
).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
245
272
|
describe("composition patterns", () => {
|
|
246
273
|
test("not negates a pattern", () => {
|
|
247
274
|
expect(
|
|
@@ -105,6 +105,22 @@ export function flattenChange(change: Change): Record<string, FlatValue> {
|
|
|
105
105
|
flat[`${prefix}/${subKey}`] = flatVal;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
} else if (
|
|
109
|
+
key === "securityLabel" &&
|
|
110
|
+
value &&
|
|
111
|
+
typeof value === "object" &&
|
|
112
|
+
!Array.isArray(value)
|
|
113
|
+
) {
|
|
114
|
+
// Security labels are change-level metadata, so expose provider/label as
|
|
115
|
+
// bare keys for filters like { scope: "security_label", provider: "..." }.
|
|
116
|
+
for (const [subKey, subValue] of Object.entries(
|
|
117
|
+
value as Record<string, unknown>,
|
|
118
|
+
)) {
|
|
119
|
+
const flatVal = toFlatValue(subValue);
|
|
120
|
+
if (flatVal !== undefined) {
|
|
121
|
+
flat[subKey] = flatVal;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
108
124
|
} else {
|
|
109
125
|
const flatVal = toFlatValue(value);
|
|
110
126
|
if (flatVal !== undefined) {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
filterPublicBuiltInDefaults,
|
|
6
6
|
} from "../base.privilege-diff.ts";
|
|
7
7
|
import type { ObjectDiffContext } from "../diff-context.ts";
|
|
8
|
+
import { diffSecurityLabels } from "../security-label.types.ts";
|
|
8
9
|
import { deepEqual, hasNonAlterableChanges } from "../utils.ts";
|
|
9
10
|
import type { Aggregate } from "./aggregate.model.ts";
|
|
10
11
|
import { AlterAggregateChangeOwner } from "./changes/aggregate.alter.ts";
|
|
@@ -19,6 +20,10 @@ import {
|
|
|
19
20
|
RevokeAggregatePrivileges,
|
|
20
21
|
RevokeGrantOptionAggregatePrivileges,
|
|
21
22
|
} from "./changes/aggregate.privilege.ts";
|
|
23
|
+
import {
|
|
24
|
+
CreateSecurityLabelOnAggregate,
|
|
25
|
+
DropSecurityLabelOnAggregate,
|
|
26
|
+
} from "./changes/aggregate.security-label.ts";
|
|
22
27
|
import type { AggregateChange } from "./changes/aggregate.types.ts";
|
|
23
28
|
|
|
24
29
|
export function diffAggregates(
|
|
@@ -51,6 +56,14 @@ export function diffAggregates(
|
|
|
51
56
|
if (aggregate.comment !== null) {
|
|
52
57
|
changes.push(new CreateCommentOnAggregate({ aggregate }));
|
|
53
58
|
}
|
|
59
|
+
for (const label of aggregate.security_labels) {
|
|
60
|
+
changes.push(
|
|
61
|
+
new CreateSecurityLabelOnAggregate({
|
|
62
|
+
aggregate,
|
|
63
|
+
securityLabel: label,
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
54
67
|
|
|
55
68
|
// PRIVILEGES: For created objects, compare against default privileges state
|
|
56
69
|
// The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
|
|
@@ -182,6 +195,26 @@ export function diffAggregates(
|
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
197
|
|
|
198
|
+
// SECURITY LABELS
|
|
199
|
+
changes.push(
|
|
200
|
+
...diffSecurityLabels<
|
|
201
|
+
CreateSecurityLabelOnAggregate | DropSecurityLabelOnAggregate
|
|
202
|
+
>(
|
|
203
|
+
mainAggregate.security_labels,
|
|
204
|
+
branchAggregate.security_labels,
|
|
205
|
+
(securityLabel) =>
|
|
206
|
+
new CreateSecurityLabelOnAggregate({
|
|
207
|
+
aggregate: branchAggregate,
|
|
208
|
+
securityLabel,
|
|
209
|
+
}),
|
|
210
|
+
(securityLabel) =>
|
|
211
|
+
new DropSecurityLabelOnAggregate({
|
|
212
|
+
aggregate: mainAggregate,
|
|
213
|
+
securityLabel,
|
|
214
|
+
}),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
|
|
185
218
|
// PRIVILEGES
|
|
186
219
|
// Filter out PUBLIC's built-in default EXECUTE privilege from main catalog
|
|
187
220
|
// (PostgreSQL grants it automatically, so we shouldn't compare it)
|