@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
package/src/core/plan/create.ts
CHANGED
|
@@ -100,7 +100,9 @@ export async function createPlan(
|
|
|
100
100
|
}
|
|
101
101
|
const resolved = await resolvePool(input, label);
|
|
102
102
|
pools.push(resolved);
|
|
103
|
-
return extractCatalog(resolved.pool
|
|
103
|
+
return extractCatalog(resolved.pool, {
|
|
104
|
+
extractRetries: options.extractRetries,
|
|
105
|
+
});
|
|
104
106
|
};
|
|
105
107
|
|
|
106
108
|
const pools: Array<{ pool: Pool; shouldClose: boolean }> = [];
|
package/src/core/plan/types.ts
CHANGED
|
@@ -165,4 +165,12 @@ export interface CreatePlanOptions {
|
|
|
165
165
|
* the output must be self-contained and not rely on statement execution order.
|
|
166
166
|
*/
|
|
167
167
|
skipDefaultPrivilegeSubtraction?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Number of retry attempts for catalog extractors when `pg_get_*def()`
|
|
170
|
+
* returns NULL for at least one row (a transient race with concurrent DDL).
|
|
171
|
+
* Total attempts is `extractRetries + 1`. When undefined, the value is read
|
|
172
|
+
* from the `PGDELTA_EXTRACT_RETRIES` environment variable, falling back to
|
|
173
|
+
* a default of 1 (i.e. the first attempt plus one retry, 2 attempts total).
|
|
174
|
+
*/
|
|
175
|
+
extractRetries?: number;
|
|
168
176
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { Change } from "./change.types.ts";
|
|
3
|
+
import { CreateIndex } from "./objects/index/changes/index.create.ts";
|
|
4
|
+
import { DropIndex } from "./objects/index/changes/index.drop.ts";
|
|
5
|
+
import { Index, type IndexProps } from "./objects/index/index.model.ts";
|
|
3
6
|
import {
|
|
4
7
|
AlterTableAddConstraint,
|
|
5
8
|
AlterTableChangeOwner,
|
|
@@ -14,7 +17,7 @@ import { CreateTable } from "./objects/table/changes/table.create.ts";
|
|
|
14
17
|
import { DropTable } from "./objects/table/changes/table.drop.ts";
|
|
15
18
|
import { GrantTablePrivileges } from "./objects/table/changes/table.privilege.ts";
|
|
16
19
|
import { Table } from "./objects/table/table.model.ts";
|
|
17
|
-
import {
|
|
20
|
+
import { normalizePostDiffChanges } from "./post-diff-normalization.ts";
|
|
18
21
|
|
|
19
22
|
const baseTableProps = {
|
|
20
23
|
schema: "public",
|
|
@@ -59,7 +62,7 @@ function integerColumn(name: string, position: number) {
|
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
describe("
|
|
65
|
+
describe("normalizePostDiffChanges", () => {
|
|
63
66
|
test("prunes same-table drop-column and drop-constraint ALTERs for replaced tables only", async () => {
|
|
64
67
|
const mainChildren = new Table({
|
|
65
68
|
...baseTableProps,
|
|
@@ -149,7 +152,7 @@ describe("normalizePostDiffCycles", () => {
|
|
|
149
152
|
preExistingGrant,
|
|
150
153
|
];
|
|
151
154
|
|
|
152
|
-
const normalized =
|
|
155
|
+
const normalized = normalizePostDiffChanges({
|
|
153
156
|
changes,
|
|
154
157
|
replacedTableIds: new Set([mainChildren.stableId]),
|
|
155
158
|
});
|
|
@@ -273,7 +276,7 @@ describe("normalizePostDiffCycles", () => {
|
|
|
273
276
|
expansionComment,
|
|
274
277
|
];
|
|
275
278
|
|
|
276
|
-
const normalized =
|
|
279
|
+
const normalized = normalizePostDiffChanges({
|
|
277
280
|
changes,
|
|
278
281
|
replacedTableIds: new Set([branchChildren.stableId]),
|
|
279
282
|
});
|
|
@@ -300,4 +303,165 @@ describe("normalizePostDiffCycles", () => {
|
|
|
300
303
|
),
|
|
301
304
|
).toHaveLength(1);
|
|
302
305
|
});
|
|
306
|
+
|
|
307
|
+
describe("restoreReplicaIdentityAfterIndexReplace", () => {
|
|
308
|
+
const baseIndexProps: IndexProps = {
|
|
309
|
+
schema: "public",
|
|
310
|
+
table_name: "replicated",
|
|
311
|
+
name: "tenant_idx",
|
|
312
|
+
storage_params: [],
|
|
313
|
+
statistics_target: [],
|
|
314
|
+
index_type: "btree",
|
|
315
|
+
tablespace: null,
|
|
316
|
+
is_unique: true,
|
|
317
|
+
is_primary: false,
|
|
318
|
+
is_exclusion: false,
|
|
319
|
+
nulls_not_distinct: false,
|
|
320
|
+
immediate: true,
|
|
321
|
+
is_clustered: false,
|
|
322
|
+
is_replica_identity: true,
|
|
323
|
+
key_columns: [],
|
|
324
|
+
column_collations: [],
|
|
325
|
+
operator_classes: [],
|
|
326
|
+
column_options: [],
|
|
327
|
+
index_expressions: null,
|
|
328
|
+
partial_predicate: null,
|
|
329
|
+
table_relkind: "r",
|
|
330
|
+
is_owned_by_constraint: false,
|
|
331
|
+
is_partitioned_index: false,
|
|
332
|
+
is_index_partition: false,
|
|
333
|
+
parent_index_name: null,
|
|
334
|
+
definition: "CREATE UNIQUE INDEX tenant_idx ON public.replicated (a)",
|
|
335
|
+
comment: null,
|
|
336
|
+
owner: "postgres",
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
function makeBranchTable(replicaIdentityIndex: string | null) {
|
|
340
|
+
return new Table({
|
|
341
|
+
...baseTableProps,
|
|
342
|
+
name: "replicated",
|
|
343
|
+
replica_identity: replicaIdentityIndex ? "i" : "d",
|
|
344
|
+
replica_identity_index: replicaIdentityIndex,
|
|
345
|
+
columns: [
|
|
346
|
+
{ ...integerColumn("id", 1), not_null: true },
|
|
347
|
+
integerColumn("a", 2),
|
|
348
|
+
],
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
test("re-emits ALTER TABLE … REPLICA IDENTITY USING INDEX after a DropIndex+CreateIndex pair", () => {
|
|
353
|
+
const branchTable = makeBranchTable("tenant_idx");
|
|
354
|
+
const oldIndex = new Index(baseIndexProps);
|
|
355
|
+
const newIndex = new Index({
|
|
356
|
+
...baseIndexProps,
|
|
357
|
+
definition:
|
|
358
|
+
"CREATE UNIQUE INDEX tenant_idx ON public.replicated (a, id)",
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const changes: Change[] = [
|
|
362
|
+
new DropIndex({ index: oldIndex }),
|
|
363
|
+
new CreateIndex({ index: newIndex, indexableObject: branchTable }),
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
const normalized = normalizePostDiffChanges({
|
|
367
|
+
changes,
|
|
368
|
+
branchTables: { [branchTable.stableId]: branchTable },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect(normalized.map((c) => c.constructor.name)).toEqual([
|
|
372
|
+
"DropIndex",
|
|
373
|
+
"CreateIndex",
|
|
374
|
+
"AlterTableSetReplicaIdentity",
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
const inserted = normalized[2] as AlterTableSetReplicaIdentity;
|
|
378
|
+
expect(inserted.mode).toBe("i");
|
|
379
|
+
expect(inserted.indexName).toBe("tenant_idx");
|
|
380
|
+
expect(inserted.requires).toEqual([
|
|
381
|
+
"table:public.replicated",
|
|
382
|
+
"index:public.replicated.tenant_idx",
|
|
383
|
+
]);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("does not double-emit when diffTables already produced an AlterTableSetReplicaIdentity for the same table", () => {
|
|
387
|
+
const branchTable = makeBranchTable("tenant_idx");
|
|
388
|
+
const oldIndex = new Index(baseIndexProps);
|
|
389
|
+
const newIndex = new Index({
|
|
390
|
+
...baseIndexProps,
|
|
391
|
+
definition:
|
|
392
|
+
"CREATE UNIQUE INDEX tenant_idx ON public.replicated (a, id)",
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const changes: Change[] = [
|
|
396
|
+
new DropIndex({ index: oldIndex }),
|
|
397
|
+
new CreateIndex({ index: newIndex, indexableObject: branchTable }),
|
|
398
|
+
new AlterTableSetReplicaIdentity({
|
|
399
|
+
table: branchTable,
|
|
400
|
+
mode: "i",
|
|
401
|
+
indexName: "tenant_idx",
|
|
402
|
+
}),
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
const normalized = normalizePostDiffChanges({
|
|
406
|
+
changes,
|
|
407
|
+
branchTables: { [branchTable.stableId]: branchTable },
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
expect(
|
|
411
|
+
normalized.filter((c) => c instanceof AlterTableSetReplicaIdentity),
|
|
412
|
+
).toHaveLength(1);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("ignores DropIndex without a matching CreateIndex (pure drop)", () => {
|
|
416
|
+
// Pure drop: the user removed the index entirely. The table.diff path is
|
|
417
|
+
// responsible for emitting the corresponding REPLICA IDENTITY DEFAULT.
|
|
418
|
+
// The post-diff pass must not synthesize a USING INDEX setter for an
|
|
419
|
+
// index that no longer exists.
|
|
420
|
+
const branchTable = makeBranchTable(null);
|
|
421
|
+
const oldIndex = new Index(baseIndexProps);
|
|
422
|
+
|
|
423
|
+
const changes: Change[] = [new DropIndex({ index: oldIndex })];
|
|
424
|
+
|
|
425
|
+
const normalized = normalizePostDiffChanges({
|
|
426
|
+
changes,
|
|
427
|
+
branchTables: { [branchTable.stableId]: branchTable },
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
expect(
|
|
431
|
+
normalized.filter((c) => c instanceof AlterTableSetReplicaIdentity),
|
|
432
|
+
).toHaveLength(0);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("ignores indexes that are not the table's replica identity", () => {
|
|
436
|
+
// The table has replica_identity = 'd', so even if some other index is
|
|
437
|
+
// being replaced, no setter should be injected.
|
|
438
|
+
const branchTable = makeBranchTable(null);
|
|
439
|
+
const otherIndex = new Index({
|
|
440
|
+
...baseIndexProps,
|
|
441
|
+
name: "some_other_idx",
|
|
442
|
+
is_replica_identity: false,
|
|
443
|
+
definition: "CREATE INDEX some_other_idx ON public.replicated (a)",
|
|
444
|
+
});
|
|
445
|
+
const newOtherIndex = new Index({
|
|
446
|
+
...baseIndexProps,
|
|
447
|
+
name: "some_other_idx",
|
|
448
|
+
is_replica_identity: false,
|
|
449
|
+
definition: "CREATE INDEX some_other_idx ON public.replicated (a, id)",
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const changes: Change[] = [
|
|
453
|
+
new DropIndex({ index: otherIndex }),
|
|
454
|
+
new CreateIndex({ index: newOtherIndex, indexableObject: branchTable }),
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
const normalized = normalizePostDiffChanges({
|
|
458
|
+
changes,
|
|
459
|
+
branchTables: { [branchTable.stableId]: branchTable },
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
expect(
|
|
463
|
+
normalized.filter((c) => c instanceof AlterTableSetReplicaIdentity),
|
|
464
|
+
).toHaveLength(0);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
303
467
|
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { Change } from "./change.types.ts";
|
|
2
|
+
import { CreateIndex } from "./objects/index/changes/index.create.ts";
|
|
3
|
+
import { DropIndex } from "./objects/index/changes/index.drop.ts";
|
|
4
|
+
import {
|
|
5
|
+
AlterTableAddConstraint,
|
|
6
|
+
AlterTableDropColumn,
|
|
7
|
+
AlterTableDropConstraint,
|
|
8
|
+
AlterTableSetReplicaIdentity,
|
|
9
|
+
AlterTableValidateConstraint,
|
|
10
|
+
} from "./objects/table/changes/table.alter.ts";
|
|
11
|
+
import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.ts";
|
|
12
|
+
import type { Table } from "./objects/table/table.model.ts";
|
|
13
|
+
import { stableId } from "./objects/utils.ts";
|
|
14
|
+
|
|
15
|
+
function constraintStableId(
|
|
16
|
+
table: { schema: string; name: string },
|
|
17
|
+
constraintName: string,
|
|
18
|
+
) {
|
|
19
|
+
return stableId.constraint(table.schema, table.name, constraintName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isSupersededByTableReplacement(
|
|
23
|
+
change: Change,
|
|
24
|
+
replacedTableIds: ReadonlySet<string>,
|
|
25
|
+
): boolean {
|
|
26
|
+
if (
|
|
27
|
+
!(change instanceof AlterTableDropColumn) &&
|
|
28
|
+
!(change instanceof AlterTableDropConstraint)
|
|
29
|
+
) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return replacedTableIds.has(change.table.stableId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Drop earlier duplicates of `AlterTableAddConstraint` /
|
|
37
|
+
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` targeting
|
|
38
|
+
* replaced tables, keeping only the last occurrence of each
|
|
39
|
+
* `(changeType, table.stableId, constraint.name)`.
|
|
40
|
+
*
|
|
41
|
+
* When `expandReplaceDependencies()` promotes a table to a full
|
|
42
|
+
* `DropTable + CreateTable` pair, it also emits one
|
|
43
|
+
* `AlterTableAddConstraint` (plus optional `VALIDATE CONSTRAINT` /
|
|
44
|
+
* `COMMENT ON CONSTRAINT`) per branch constraint. If `diffTables()` already
|
|
45
|
+
* emitted the same change for a shape flip or a new constraint on that
|
|
46
|
+
* table, the plan ends up with two identical `ALTER TABLE ... ADD
|
|
47
|
+
* CONSTRAINT ...` statements and PostgreSQL fails at apply time with
|
|
48
|
+
* `constraint "..." for relation "..." already exists`. Because
|
|
49
|
+
* `expandReplaceDependencies()` appends its additions after the original
|
|
50
|
+
* `diffTables()` output, the last occurrence is the expansion's emission —
|
|
51
|
+
* keeping it preserves correctness while removing the duplicate.
|
|
52
|
+
*/
|
|
53
|
+
function dropReplacedTableDuplicateConstraintChanges(
|
|
54
|
+
changes: Change[],
|
|
55
|
+
replacedTableIds: ReadonlySet<string>,
|
|
56
|
+
): Change[] {
|
|
57
|
+
if (replacedTableIds.size === 0) return changes;
|
|
58
|
+
|
|
59
|
+
const keyFor = (change: Change): string | null => {
|
|
60
|
+
if (
|
|
61
|
+
!(change instanceof AlterTableAddConstraint) &&
|
|
62
|
+
!(change instanceof AlterTableValidateConstraint) &&
|
|
63
|
+
!(change instanceof CreateCommentOnConstraint)
|
|
64
|
+
) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (!replacedTableIds.has(change.table.stableId)) return null;
|
|
68
|
+
const tag =
|
|
69
|
+
change instanceof AlterTableAddConstraint
|
|
70
|
+
? "add"
|
|
71
|
+
: change instanceof AlterTableValidateConstraint
|
|
72
|
+
? "validate"
|
|
73
|
+
: "comment";
|
|
74
|
+
return `${tag}:${constraintStableId(change.table, change.constraint.name)}`;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const seen = new Set<string>();
|
|
78
|
+
const reversedKept: Change[] = [];
|
|
79
|
+
let mutated = false;
|
|
80
|
+
|
|
81
|
+
// Walk backwards: the first encounter of each key corresponds to its LAST
|
|
82
|
+
// occurrence in the original order. `expandReplaceDependencies()` appends
|
|
83
|
+
// additions after the original changes, so "last wins" keeps the
|
|
84
|
+
// expansion's emission and drops the earlier diffTables duplicate.
|
|
85
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
86
|
+
const change = changes[i] as Change;
|
|
87
|
+
const key = keyFor(change);
|
|
88
|
+
if (key !== null) {
|
|
89
|
+
if (seen.has(key)) {
|
|
90
|
+
mutated = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
seen.add(key);
|
|
94
|
+
}
|
|
95
|
+
reversedKept.push(change);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return mutated ? reversedKept.reverse() : changes;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Re-emit `ALTER TABLE ... REPLICA IDENTITY USING INDEX <idx>` after any
|
|
103
|
+
* `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica-identity
|
|
104
|
+
* index of a branch table.
|
|
105
|
+
*
|
|
106
|
+
* Background: PostgreSQL silently flips a table's `relreplident` to `'d'`
|
|
107
|
+
* (DEFAULT) when the index it points to is dropped. `CREATE INDEX` cannot
|
|
108
|
+
* restore the marker — only `ALTER TABLE ... REPLICA IDENTITY USING INDEX`
|
|
109
|
+
* can. When both main and branch carry `replica_identity = 'i'` pointing at
|
|
110
|
+
* the same index name, `diffTables()` emits no replica-identity change of its
|
|
111
|
+
* own, so the marker would be lost on apply.
|
|
112
|
+
*
|
|
113
|
+
* This is a whole-plan interaction: `diffTables()` cannot detect it without
|
|
114
|
+
* also looking at index changes. Per the "whole-plan interactions belong in
|
|
115
|
+
* post-diff normalization" rule in the package CLAUDE.md, the restoration
|
|
116
|
+
* lives here.
|
|
117
|
+
*
|
|
118
|
+
* Insertion is idempotent: if `diffTables()` already emitted the same
|
|
119
|
+
* `AlterTableSetReplicaIdentity` for this table (e.g. when the user is also
|
|
120
|
+
* switching the replica-identity index name in the same migration), no
|
|
121
|
+
* duplicate is added.
|
|
122
|
+
*/
|
|
123
|
+
function restoreReplicaIdentityAfterIndexReplace(
|
|
124
|
+
changes: Change[],
|
|
125
|
+
branchTables: Record<string, Table>,
|
|
126
|
+
): Change[] {
|
|
127
|
+
// Build the index-stable-id → owning-table map from branch state. Only
|
|
128
|
+
// tables in 'i' mode contribute, and only those whose configured index name
|
|
129
|
+
// is non-null (the extractor returns null for any other mode).
|
|
130
|
+
const replicaIdentityIndexToTable = new Map<string, Table>();
|
|
131
|
+
for (const table of Object.values(branchTables)) {
|
|
132
|
+
if (table.replica_identity !== "i" || !table.replica_identity_index) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const indexId = stableId.index(
|
|
136
|
+
table.schema,
|
|
137
|
+
table.name,
|
|
138
|
+
table.replica_identity_index,
|
|
139
|
+
);
|
|
140
|
+
replicaIdentityIndexToTable.set(indexId, table);
|
|
141
|
+
}
|
|
142
|
+
if (replicaIdentityIndexToTable.size === 0) return changes;
|
|
143
|
+
|
|
144
|
+
// Find the indexes that are both dropped AND created in this plan. A pure
|
|
145
|
+
// drop or a pure create is handled by `diffTables()` directly (the table's
|
|
146
|
+
// replica_identity / replica_identity_index fields will have changed). The
|
|
147
|
+
// hole is specifically the drop+create pair that recreates the same name.
|
|
148
|
+
const droppedIndexIds = new Set<string>();
|
|
149
|
+
const createdIndexIds = new Set<string>();
|
|
150
|
+
for (const change of changes) {
|
|
151
|
+
if (change instanceof DropIndex) {
|
|
152
|
+
droppedIndexIds.add(change.index.stableId);
|
|
153
|
+
} else if (change instanceof CreateIndex) {
|
|
154
|
+
createdIndexIds.add(change.index.stableId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const replacedIndexIds = new Set<string>();
|
|
158
|
+
for (const id of droppedIndexIds) {
|
|
159
|
+
if (createdIndexIds.has(id) && replicaIdentityIndexToTable.has(id)) {
|
|
160
|
+
replacedIndexIds.add(id);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (replacedIndexIds.size === 0) return changes;
|
|
164
|
+
|
|
165
|
+
// Skip tables for which `diffTables()` already emitted a replica-identity
|
|
166
|
+
// setter — re-emitting would produce a redundant ALTER TABLE (harmless on
|
|
167
|
+
// apply, but noisy in plan output).
|
|
168
|
+
const tablesWithExistingReplicaIdentitySetter = new Set<string>();
|
|
169
|
+
for (const change of changes) {
|
|
170
|
+
if (change instanceof AlterTableSetReplicaIdentity) {
|
|
171
|
+
tablesWithExistingReplicaIdentitySetter.add(change.table.stableId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Insert one `AlterTableSetReplicaIdentity` per replaced index, immediately
|
|
176
|
+
// after the matching `CreateIndex`. The change's `requires` already names
|
|
177
|
+
// both the table and the recreated index, so the topo sort orders it
|
|
178
|
+
// correctly relative to the surrounding DDL.
|
|
179
|
+
const result: Change[] = [];
|
|
180
|
+
for (const change of changes) {
|
|
181
|
+
result.push(change);
|
|
182
|
+
if (
|
|
183
|
+
!(change instanceof CreateIndex) ||
|
|
184
|
+
!replacedIndexIds.has(change.index.stableId)
|
|
185
|
+
) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const table = replicaIdentityIndexToTable.get(change.index.stableId);
|
|
189
|
+
if (!table) continue;
|
|
190
|
+
if (tablesWithExistingReplicaIdentitySetter.has(table.stableId)) continue;
|
|
191
|
+
|
|
192
|
+
result.push(
|
|
193
|
+
new AlterTableSetReplicaIdentity({
|
|
194
|
+
table,
|
|
195
|
+
mode: "i",
|
|
196
|
+
indexName: table.replica_identity_index,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
// Mark as emitted so a second replaced index on the same table — if that
|
|
200
|
+
// ever arises — doesn't double-emit.
|
|
201
|
+
tablesWithExistingReplicaIdentitySetter.add(table.stableId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Apply structural rewrites to the change list that are only obvious once
|
|
209
|
+
* every object diff has been collected. This pass does NOT prevent dependency
|
|
210
|
+
* cycles — that responsibility now lives in the sort phase, where
|
|
211
|
+
* `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
|
|
212
|
+
* that edge filtering can't break (FK SCC of dropped tables,
|
|
213
|
+
* AlterPublicationDropTables ↔ AlterTableDropColumn, …).
|
|
214
|
+
*
|
|
215
|
+
* Concretely, this pass:
|
|
216
|
+
*
|
|
217
|
+
* - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
|
|
218
|
+
* changes that are made redundant by an expansion-emitted
|
|
219
|
+
* `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
|
|
220
|
+
* would try to drop a column that no longer exists in the freshly
|
|
221
|
+
* recreated table.
|
|
222
|
+
* - Dedupes duplicate `AlterTableAddConstraint` /
|
|
223
|
+
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
|
|
224
|
+
* produced when `diffTables()` and `expandReplaceDependencies()` both
|
|
225
|
+
* emit the same constraint operation for a replaced table. Last write
|
|
226
|
+
* wins so the expansion's emission survives.
|
|
227
|
+
* - Re-emits `ALTER TABLE ... REPLICA IDENTITY USING INDEX <idx>` after any
|
|
228
|
+
* `DropIndex(idx) + CreateIndex(idx)` pair where `idx` is the replica
|
|
229
|
+
* identity index of a branch table — Postgres silently clears the marker
|
|
230
|
+
* when the underlying index is dropped, and `CREATE INDEX` cannot restore
|
|
231
|
+
* it.
|
|
232
|
+
*
|
|
233
|
+
* Object-local PostgreSQL semantics (for example owned-sequence cascades)
|
|
234
|
+
* stay in the corresponding `diff*` function instead of this pass.
|
|
235
|
+
*/
|
|
236
|
+
export function normalizePostDiffChanges({
|
|
237
|
+
changes,
|
|
238
|
+
replacedTableIds = new Set<string>(),
|
|
239
|
+
branchTables = {},
|
|
240
|
+
}: {
|
|
241
|
+
changes: Change[];
|
|
242
|
+
replacedTableIds?: ReadonlySet<string>;
|
|
243
|
+
branchTables?: Record<string, Table>;
|
|
244
|
+
}): Change[] {
|
|
245
|
+
const restoredChanges = restoreReplicaIdentityAfterIndexReplace(
|
|
246
|
+
changes,
|
|
247
|
+
branchTables,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(
|
|
251
|
+
restoredChanges,
|
|
252
|
+
replacedTableIds,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (replacedTableIds.size === 0) return dedupedChanges;
|
|
256
|
+
|
|
257
|
+
return dedupedChanges.filter(
|
|
258
|
+
(change) => !isSupersededByTableReplacement(change, replacedTableIds),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
@@ -284,7 +284,7 @@ function tryBreakPublicationColumnCycle(
|
|
|
284
284
|
|
|
285
285
|
// Verify the table is NOT itself being dropped. If `DropTable(T)` is in
|
|
286
286
|
// the same phase, the existing structural rewrites in
|
|
287
|
-
// `post-diff-
|
|
287
|
+
// `post-diff-normalization.ts` (replace-expansion superseded filter)
|
|
288
288
|
// already prune the redundant `AlterTableDropColumn`, so we should not
|
|
289
289
|
// see this combination here. Be defensive and bail anyway — flipping
|
|
290
290
|
// `omitTableRequirement` when T is being dropped would let the column
|
package/src/core/sort/utils.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Change } from "../change.types.ts";
|
|
2
|
+
import {
|
|
3
|
+
AlterTableAlterColumnDropDefault,
|
|
4
|
+
AlterTableAlterColumnDropIdentity,
|
|
5
|
+
AlterTableAlterColumnType,
|
|
6
|
+
} from "../objects/table/changes/table.alter.ts";
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Execution phases for changes.
|
|
@@ -30,6 +35,16 @@ export function isMetadataStableId(stableId: string): boolean {
|
|
|
30
35
|
* - ALTER operations with scope="privilege" → create_alter_object phase (metadata changes)
|
|
31
36
|
* - ALTER operations that drop actual objects → drop phase (destructive ALTER)
|
|
32
37
|
* - ALTER operations that don't drop objects → create_alter_object phase (non-destructive ALTER)
|
|
38
|
+
*
|
|
39
|
+
* Dependency-breaking ALTERs that remove a `pg_depend` edge to another
|
|
40
|
+
* object that may be dropped in the same plan (for example
|
|
41
|
+
* `ALTER COLUMN ... DROP DEFAULT` releasing a sequence reference, or
|
|
42
|
+
* `ALTER COLUMN ... TYPE <built-in>` releasing a user-defined type
|
|
43
|
+
* reference) are routed to the drop phase. The drop phase sorts in reverse
|
|
44
|
+
* dependency order using the main catalog, so the catalog edges already
|
|
45
|
+
* in `pg_depend` order the ALTER before any dependent `DROP TYPE` /
|
|
46
|
+
* `DROP SEQUENCE` / `DROP FUNCTION` and PostgreSQL no longer rejects the
|
|
47
|
+
* drop with error 2BP01.
|
|
33
48
|
*/
|
|
34
49
|
export function getExecutionPhase(change: Change): Phase {
|
|
35
50
|
// DROP operations always go to drop phase
|
|
@@ -60,6 +75,29 @@ export function getExecutionPhase(change: Change): Phase {
|
|
|
60
75
|
return "drop";
|
|
61
76
|
}
|
|
62
77
|
|
|
78
|
+
// Dependency-breaking column ALTERs that release a pg_depend edge.
|
|
79
|
+
// Routing these to the drop phase lets the existing catalog dependency
|
|
80
|
+
// edges (column → sequence, column → identity sequence) order them
|
|
81
|
+
// before the matching DROP statement.
|
|
82
|
+
if (
|
|
83
|
+
change instanceof AlterTableAlterColumnDropDefault ||
|
|
84
|
+
change instanceof AlterTableAlterColumnDropIdentity
|
|
85
|
+
) {
|
|
86
|
+
return "drop";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ALTER COLUMN ... TYPE only safely runs in the drop phase when the
|
|
90
|
+
// target type is built-in. For user-defined target types we cannot tell
|
|
91
|
+
// here whether the type is created in the same plan, and the create
|
|
92
|
+
// happens in create_alter phase, so we keep the alter in that phase to
|
|
93
|
+
// preserve the create-then-alter ordering.
|
|
94
|
+
if (
|
|
95
|
+
change instanceof AlterTableAlterColumnType &&
|
|
96
|
+
!change.column.is_custom_type
|
|
97
|
+
) {
|
|
98
|
+
return "drop";
|
|
99
|
+
}
|
|
100
|
+
|
|
63
101
|
// Non-destructive ALTER (ADD COLUMN, GRANT, etc.) → create_alter phase
|
|
64
102
|
return "create_alter_object";
|
|
65
103
|
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { AlterTableAddConstraint, AlterTableDropColumn, AlterTableDropConstraint, AlterTableValidateConstraint, } from "./objects/table/changes/table.alter.js";
|
|
2
|
-
import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.js";
|
|
3
|
-
import { stableId } from "./objects/utils.js";
|
|
4
|
-
function constraintStableId(table, constraintName) {
|
|
5
|
-
return stableId.constraint(table.schema, table.name, constraintName);
|
|
6
|
-
}
|
|
7
|
-
function isSupersededByTableReplacement(change, replacedTableIds) {
|
|
8
|
-
if (!(change instanceof AlterTableDropColumn) &&
|
|
9
|
-
!(change instanceof AlterTableDropConstraint)) {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
return replacedTableIds.has(change.table.stableId);
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Drop earlier duplicates of `AlterTableAddConstraint` /
|
|
16
|
-
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` targeting
|
|
17
|
-
* replaced tables, keeping only the last occurrence of each
|
|
18
|
-
* `(changeType, table.stableId, constraint.name)`.
|
|
19
|
-
*
|
|
20
|
-
* When `expandReplaceDependencies()` promotes a table to a full
|
|
21
|
-
* `DropTable + CreateTable` pair, it also emits one
|
|
22
|
-
* `AlterTableAddConstraint` (plus optional `VALIDATE CONSTRAINT` /
|
|
23
|
-
* `COMMENT ON CONSTRAINT`) per branch constraint. If `diffTables()` already
|
|
24
|
-
* emitted the same change for a shape flip or a new constraint on that
|
|
25
|
-
* table, the plan ends up with two identical `ALTER TABLE ... ADD
|
|
26
|
-
* CONSTRAINT ...` statements and PostgreSQL fails at apply time with
|
|
27
|
-
* `constraint "..." for relation "..." already exists`. Because
|
|
28
|
-
* `expandReplaceDependencies()` appends its additions after the original
|
|
29
|
-
* `diffTables()` output, the last occurrence is the expansion's emission —
|
|
30
|
-
* keeping it preserves correctness while removing the duplicate.
|
|
31
|
-
*/
|
|
32
|
-
function dropReplacedTableDuplicateConstraintChanges(changes, replacedTableIds) {
|
|
33
|
-
if (replacedTableIds.size === 0)
|
|
34
|
-
return changes;
|
|
35
|
-
const keyFor = (change) => {
|
|
36
|
-
if (!(change instanceof AlterTableAddConstraint) &&
|
|
37
|
-
!(change instanceof AlterTableValidateConstraint) &&
|
|
38
|
-
!(change instanceof CreateCommentOnConstraint)) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
if (!replacedTableIds.has(change.table.stableId))
|
|
42
|
-
return null;
|
|
43
|
-
const tag = change instanceof AlterTableAddConstraint
|
|
44
|
-
? "add"
|
|
45
|
-
: change instanceof AlterTableValidateConstraint
|
|
46
|
-
? "validate"
|
|
47
|
-
: "comment";
|
|
48
|
-
return `${tag}:${constraintStableId(change.table, change.constraint.name)}`;
|
|
49
|
-
};
|
|
50
|
-
const seen = new Set();
|
|
51
|
-
const reversedKept = [];
|
|
52
|
-
let mutated = false;
|
|
53
|
-
// Walk backwards: the first encounter of each key corresponds to its LAST
|
|
54
|
-
// occurrence in the original order. `expandReplaceDependencies()` appends
|
|
55
|
-
// additions after the original changes, so "last wins" keeps the
|
|
56
|
-
// expansion's emission and drops the earlier diffTables duplicate.
|
|
57
|
-
for (let i = changes.length - 1; i >= 0; i--) {
|
|
58
|
-
const change = changes[i];
|
|
59
|
-
const key = keyFor(change);
|
|
60
|
-
if (key !== null) {
|
|
61
|
-
if (seen.has(key)) {
|
|
62
|
-
mutated = true;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
seen.add(key);
|
|
66
|
-
}
|
|
67
|
-
reversedKept.push(change);
|
|
68
|
-
}
|
|
69
|
-
return mutated ? reversedKept.reverse() : changes;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Apply structural rewrites to the change list that are only obvious once
|
|
73
|
-
* every object diff has been collected. This pass does NOT prevent dependency
|
|
74
|
-
* cycles — that responsibility now lives in the sort phase, where
|
|
75
|
-
* `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
|
|
76
|
-
* that edge filtering can't break (FK SCC of dropped tables,
|
|
77
|
-
* AlterPublicationDropTables ↔ AlterTableDropColumn, …).
|
|
78
|
-
*
|
|
79
|
-
* Concretely, this pass:
|
|
80
|
-
*
|
|
81
|
-
* - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
|
|
82
|
-
* changes that are made redundant by an expansion-emitted
|
|
83
|
-
* `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
|
|
84
|
-
* would try to drop a column that no longer exists in the freshly
|
|
85
|
-
* recreated table.
|
|
86
|
-
* - Dedupes duplicate `AlterTableAddConstraint` /
|
|
87
|
-
* `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
|
|
88
|
-
* produced when `diffTables()` and `expandReplaceDependencies()` both
|
|
89
|
-
* emit the same constraint operation for a replaced table. Last write
|
|
90
|
-
* wins so the expansion's emission survives.
|
|
91
|
-
*
|
|
92
|
-
* Object-local PostgreSQL semantics (for example owned-sequence cascades)
|
|
93
|
-
* stay in the corresponding `diff*` function instead of this pass.
|
|
94
|
-
*/
|
|
95
|
-
export function normalizePostDiffCycles({ changes, replacedTableIds = new Set(), }) {
|
|
96
|
-
const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(changes, replacedTableIds);
|
|
97
|
-
if (replacedTableIds.size === 0)
|
|
98
|
-
return dedupedChanges;
|
|
99
|
-
return dedupedChanges.filter((change) => !isSupersededByTableReplacement(change, replacedTableIds));
|
|
100
|
-
}
|