@supabase/pg-delta 1.0.0-alpha.21 → 1.0.0-alpha.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/core/catalog.diff.js +4 -3
  2. package/dist/core/catalog.model.d.ts +8 -1
  3. package/dist/core/catalog.model.js +9 -8
  4. package/dist/core/expand-replace-dependencies.js +23 -0
  5. package/dist/core/objects/extract-with-retry.d.ts +36 -0
  6. package/dist/core/objects/extract-with-retry.js +51 -0
  7. package/dist/core/objects/index/index.diff.js +0 -1
  8. package/dist/core/objects/index/index.model.d.ts +2 -3
  9. package/dist/core/objects/index/index.model.js +17 -6
  10. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +2 -1
  11. package/dist/core/objects/materialized-view/materialized-view.model.js +20 -4
  12. package/dist/core/objects/procedure/procedure.model.d.ts +2 -1
  13. package/dist/core/objects/procedure/procedure.model.js +20 -4
  14. package/dist/core/objects/rls-policy/rls-policy.diff.js +13 -1
  15. package/dist/core/objects/rule/rule.model.d.ts +2 -1
  16. package/dist/core/objects/rule/rule.model.js +20 -3
  17. package/dist/core/objects/sequence/sequence.diff.d.ts +2 -1
  18. package/dist/core/objects/sequence/sequence.diff.js +28 -4
  19. package/dist/core/objects/table/changes/table.alter.d.ts +12 -1
  20. package/dist/core/objects/table/changes/table.alter.js +20 -2
  21. package/dist/core/objects/table/table.diff.js +19 -15
  22. package/dist/core/objects/table/table.model.d.ts +6 -1
  23. package/dist/core/objects/table/table.model.js +40 -5
  24. package/dist/core/objects/trigger/trigger.model.d.ts +2 -1
  25. package/dist/core/objects/trigger/trigger.model.js +20 -4
  26. package/dist/core/objects/utils.d.ts +1 -0
  27. package/dist/core/objects/utils.js +3 -0
  28. package/dist/core/objects/view/view.model.d.ts +2 -1
  29. package/dist/core/objects/view/view.model.js +20 -4
  30. package/dist/core/plan/create.js +3 -1
  31. package/dist/core/plan/types.d.ts +8 -0
  32. package/dist/core/{post-diff-cycle-breaking.d.ts → post-diff-normalization.d.ts} +8 -1
  33. package/dist/core/post-diff-normalization.js +202 -0
  34. package/dist/core/sort/cycle-breakers.js +1 -1
  35. package/dist/core/sort/utils.d.ts +10 -0
  36. package/dist/core/sort/utils.js +28 -0
  37. package/package.json +1 -1
  38. package/src/core/catalog.diff.ts +4 -2
  39. package/src/core/catalog.model.ts +20 -8
  40. package/src/core/expand-replace-dependencies.test.ts +131 -0
  41. package/src/core/expand-replace-dependencies.ts +24 -0
  42. package/src/core/objects/extract-with-retry.test.ts +143 -0
  43. package/src/core/objects/extract-with-retry.ts +87 -0
  44. package/src/core/objects/index/index.diff.ts +0 -1
  45. package/src/core/objects/index/index.model.test.ts +37 -1
  46. package/src/core/objects/index/index.model.ts +25 -6
  47. package/src/core/objects/materialized-view/materialized-view.model.test.ts +93 -0
  48. package/src/core/objects/materialized-view/materialized-view.model.ts +27 -4
  49. package/src/core/objects/procedure/procedure.model.test.ts +117 -0
  50. package/src/core/objects/procedure/procedure.model.ts +28 -5
  51. package/src/core/objects/rls-policy/rls-policy.diff.ts +19 -1
  52. package/src/core/objects/rule/rule.model.test.ts +99 -0
  53. package/src/core/objects/rule/rule.model.ts +28 -4
  54. package/src/core/objects/sequence/sequence.diff.test.ts +87 -0
  55. package/src/core/objects/sequence/sequence.diff.ts +31 -6
  56. package/src/core/objects/table/changes/table.alter.test.ts +13 -21
  57. package/src/core/objects/table/changes/table.alter.ts +30 -3
  58. package/src/core/objects/table/table.diff.ts +24 -19
  59. package/src/core/objects/table/table.model.test.ts +209 -0
  60. package/src/core/objects/table/table.model.ts +52 -7
  61. package/src/core/objects/trigger/trigger.model.test.ts +113 -0
  62. package/src/core/objects/trigger/trigger.model.ts +28 -5
  63. package/src/core/objects/utils.ts +3 -0
  64. package/src/core/objects/view/view.model.test.ts +90 -0
  65. package/src/core/objects/view/view.model.ts +28 -5
  66. package/src/core/plan/create.ts +3 -1
  67. package/src/core/plan/types.ts +8 -0
  68. package/src/core/{post-diff-cycle-breaking.test.ts → post-diff-normalization.test.ts} +168 -4
  69. package/src/core/post-diff-normalization.ts +260 -0
  70. package/src/core/sort/cycle-breakers.ts +1 -1
  71. package/src/core/sort/utils.ts +38 -0
  72. package/dist/core/post-diff-cycle-breaking.js +0 -100
  73. package/src/core/post-diff-cycle-breaking.ts +0 -138
@@ -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
- }
@@ -1,138 +0,0 @@
1
- import type { Change } from "./change.types.ts";
2
- import {
3
- AlterTableAddConstraint,
4
- AlterTableDropColumn,
5
- AlterTableDropConstraint,
6
- AlterTableValidateConstraint,
7
- } from "./objects/table/changes/table.alter.ts";
8
- import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.ts";
9
- import { stableId } from "./objects/utils.ts";
10
-
11
- function constraintStableId(
12
- table: { schema: string; name: string },
13
- constraintName: string,
14
- ) {
15
- return stableId.constraint(table.schema, table.name, constraintName);
16
- }
17
-
18
- function isSupersededByTableReplacement(
19
- change: Change,
20
- replacedTableIds: ReadonlySet<string>,
21
- ): boolean {
22
- if (
23
- !(change instanceof AlterTableDropColumn) &&
24
- !(change instanceof AlterTableDropConstraint)
25
- ) {
26
- return false;
27
- }
28
- return replacedTableIds.has(change.table.stableId);
29
- }
30
-
31
- /**
32
- * Drop earlier duplicates of `AlterTableAddConstraint` /
33
- * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` targeting
34
- * replaced tables, keeping only the last occurrence of each
35
- * `(changeType, table.stableId, constraint.name)`.
36
- *
37
- * When `expandReplaceDependencies()` promotes a table to a full
38
- * `DropTable + CreateTable` pair, it also emits one
39
- * `AlterTableAddConstraint` (plus optional `VALIDATE CONSTRAINT` /
40
- * `COMMENT ON CONSTRAINT`) per branch constraint. If `diffTables()` already
41
- * emitted the same change for a shape flip or a new constraint on that
42
- * table, the plan ends up with two identical `ALTER TABLE ... ADD
43
- * CONSTRAINT ...` statements and PostgreSQL fails at apply time with
44
- * `constraint "..." for relation "..." already exists`. Because
45
- * `expandReplaceDependencies()` appends its additions after the original
46
- * `diffTables()` output, the last occurrence is the expansion's emission —
47
- * keeping it preserves correctness while removing the duplicate.
48
- */
49
- function dropReplacedTableDuplicateConstraintChanges(
50
- changes: Change[],
51
- replacedTableIds: ReadonlySet<string>,
52
- ): Change[] {
53
- if (replacedTableIds.size === 0) return changes;
54
-
55
- const keyFor = (change: Change): string | null => {
56
- if (
57
- !(change instanceof AlterTableAddConstraint) &&
58
- !(change instanceof AlterTableValidateConstraint) &&
59
- !(change instanceof CreateCommentOnConstraint)
60
- ) {
61
- return null;
62
- }
63
- if (!replacedTableIds.has(change.table.stableId)) return null;
64
- const tag =
65
- change instanceof AlterTableAddConstraint
66
- ? "add"
67
- : change instanceof AlterTableValidateConstraint
68
- ? "validate"
69
- : "comment";
70
- return `${tag}:${constraintStableId(change.table, change.constraint.name)}`;
71
- };
72
-
73
- const seen = new Set<string>();
74
- const reversedKept: Change[] = [];
75
- let mutated = false;
76
-
77
- // Walk backwards: the first encounter of each key corresponds to its LAST
78
- // occurrence in the original order. `expandReplaceDependencies()` appends
79
- // additions after the original changes, so "last wins" keeps the
80
- // expansion's emission and drops the earlier diffTables duplicate.
81
- for (let i = changes.length - 1; i >= 0; i--) {
82
- const change = changes[i] as Change;
83
- const key = keyFor(change);
84
- if (key !== null) {
85
- if (seen.has(key)) {
86
- mutated = true;
87
- continue;
88
- }
89
- seen.add(key);
90
- }
91
- reversedKept.push(change);
92
- }
93
-
94
- return mutated ? reversedKept.reverse() : changes;
95
- }
96
-
97
- /**
98
- * Apply structural rewrites to the change list that are only obvious once
99
- * every object diff has been collected. This pass does NOT prevent dependency
100
- * cycles — that responsibility now lives in the sort phase, where
101
- * `sortPhaseChanges` invokes `tryBreakCycleByChangeInjection` lazily on cycles
102
- * that edge filtering can't break (FK SCC of dropped tables,
103
- * AlterPublicationDropTables ↔ AlterTableDropColumn, …).
104
- *
105
- * Concretely, this pass:
106
- *
107
- * - Prunes `AlterTableDropColumn(T.*)` / `AlterTableDropConstraint(T.*)`
108
- * changes that are made redundant by an expansion-emitted
109
- * `DropTable(T) + CreateTable(T)` pair. Without this, the apply phase
110
- * would try to drop a column that no longer exists in the freshly
111
- * recreated table.
112
- * - Dedupes duplicate `AlterTableAddConstraint` /
113
- * `AlterTableValidateConstraint` / `CreateCommentOnConstraint` changes
114
- * produced when `diffTables()` and `expandReplaceDependencies()` both
115
- * emit the same constraint operation for a replaced table. Last write
116
- * wins so the expansion's emission survives.
117
- *
118
- * Object-local PostgreSQL semantics (for example owned-sequence cascades)
119
- * stay in the corresponding `diff*` function instead of this pass.
120
- */
121
- export function normalizePostDiffCycles({
122
- changes,
123
- replacedTableIds = new Set<string>(),
124
- }: {
125
- changes: Change[];
126
- replacedTableIds?: ReadonlySet<string>;
127
- }): Change[] {
128
- const dedupedChanges = dropReplacedTableDuplicateConstraintChanges(
129
- changes,
130
- replacedTableIds,
131
- );
132
-
133
- if (replacedTableIds.size === 0) return dedupedChanges;
134
-
135
- return dedupedChanges.filter(
136
- (change) => !isSupersededByTableReplacement(change, replacedTableIds),
137
- );
138
- }