@supabase/pg-delta 1.0.0-alpha.27 → 1.0.0-alpha.29
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 +22 -3
- package/dist/core/expand-replace-dependencies.d.ts +3 -1
- package/dist/core/expand-replace-dependencies.js +117 -7
- package/dist/core/objects/base.change.d.ts +12 -0
- package/dist/core/objects/base.change.js +14 -0
- package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +1 -0
- package/dist/core/objects/materialized-view/materialized-view.diff.js +59 -59
- package/dist/core/objects/table/changes/table.alter.d.ts +1 -0
- package/dist/core/objects/table/changes/table.alter.js +8 -0
- package/dist/core/objects/view/view.diff.d.ts +1 -0
- package/dist/core/objects/view/view.diff.js +35 -34
- package/dist/core/sort/cycle-breakers.js +8 -3
- package/dist/core/sort/graph-builder.js +6 -0
- package/package.json +2 -2
- package/src/core/catalog.diff.test.ts +173 -0
- package/src/core/catalog.diff.ts +24 -3
- package/src/core/expand-replace-dependencies.test.ts +282 -0
- package/src/core/expand-replace-dependencies.ts +165 -7
- package/src/core/objects/base.change.ts +15 -0
- package/src/core/objects/materialized-view/materialized-view.diff.test.ts +3 -2
- package/src/core/objects/materialized-view/materialized-view.diff.ts +99 -92
- package/src/core/objects/table/changes/table.alter.ts +9 -0
- package/src/core/objects/view/view.diff.ts +67 -60
- package/src/core/sort/cycle-breakers.test.ts +126 -0
- package/src/core/sort/cycle-breakers.ts +12 -2
- package/src/core/sort/graph-builder.ts +6 -0
- package/src/core/sort/sort-changes.test.ts +73 -1
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type { Catalog } from "./catalog.model.ts";
|
|
2
2
|
import type { Change } from "./change.types.ts";
|
|
3
|
+
import type { ObjectDiffContext } from "./objects/diff-context.ts";
|
|
3
4
|
import { CreateDomain } from "./objects/domain/changes/domain.create.ts";
|
|
4
5
|
import { DropDomain } from "./objects/domain/changes/domain.drop.ts";
|
|
5
6
|
import { CreateIndex } from "./objects/index/changes/index.create.ts";
|
|
6
7
|
import { DropIndex } from "./objects/index/changes/index.drop.ts";
|
|
7
8
|
import { CreateMaterializedView } from "./objects/materialized-view/changes/materialized-view.create.ts";
|
|
8
9
|
import { DropMaterializedView } from "./objects/materialized-view/changes/materialized-view.drop.ts";
|
|
10
|
+
import { buildCreateMaterializedViewChanges } from "./objects/materialized-view/materialized-view.diff.ts";
|
|
9
11
|
import { CreateProcedure } from "./objects/procedure/changes/procedure.create.ts";
|
|
10
12
|
import { DropProcedure } from "./objects/procedure/changes/procedure.drop.ts";
|
|
13
|
+
import { CreateCommentOnRlsPolicy } from "./objects/rls-policy/changes/rls-policy.comment.ts";
|
|
14
|
+
import { CreateRlsPolicy } from "./objects/rls-policy/changes/rls-policy.create.ts";
|
|
15
|
+
import { DropRlsPolicy } from "./objects/rls-policy/changes/rls-policy.drop.ts";
|
|
11
16
|
import { AlterTableAddConstraint } from "./objects/table/changes/table.alter.ts";
|
|
12
17
|
import { CreateCommentOnConstraint } from "./objects/table/changes/table.comment.ts";
|
|
13
18
|
import { CreateTable } from "./objects/table/changes/table.create.ts";
|
|
@@ -21,6 +26,7 @@ import { DropRange } from "./objects/type/range/changes/range.drop.ts";
|
|
|
21
26
|
import { stableId } from "./objects/utils.ts";
|
|
22
27
|
import { CreateView } from "./objects/view/changes/view.create.ts";
|
|
23
28
|
import { DropView } from "./objects/view/changes/view.drop.ts";
|
|
29
|
+
import { buildCreateViewChanges } from "./objects/view/view.diff.ts";
|
|
24
30
|
|
|
25
31
|
type ResolvedObject =
|
|
26
32
|
| {
|
|
@@ -49,6 +55,11 @@ type ResolvedObject =
|
|
|
49
55
|
main: Catalog["procedures"][string];
|
|
50
56
|
branch: Catalog["procedures"][string];
|
|
51
57
|
}
|
|
58
|
+
| {
|
|
59
|
+
kind: "rls_policy";
|
|
60
|
+
main: Catalog["rlsPolicies"][string];
|
|
61
|
+
branch: Catalog["rlsPolicies"][string];
|
|
62
|
+
}
|
|
52
63
|
| {
|
|
53
64
|
kind: "enum";
|
|
54
65
|
main: Catalog["enums"][string];
|
|
@@ -87,10 +98,15 @@ export function expandReplaceDependencies({
|
|
|
87
98
|
changes,
|
|
88
99
|
mainCatalog,
|
|
89
100
|
branchCatalog,
|
|
101
|
+
diffContext,
|
|
90
102
|
}: {
|
|
91
103
|
changes: Change[];
|
|
92
104
|
mainCatalog: Catalog;
|
|
93
105
|
branchCatalog: Catalog;
|
|
106
|
+
diffContext?: Pick<
|
|
107
|
+
ObjectDiffContext,
|
|
108
|
+
"version" | "currentUser" | "defaultPrivilegeState"
|
|
109
|
+
>;
|
|
94
110
|
}): ExpandReplaceDependenciesResult {
|
|
95
111
|
const createdIds = new Set<string>();
|
|
96
112
|
const droppedIds = new Set<string>();
|
|
@@ -107,6 +123,16 @@ export function expandReplaceDependencies({
|
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
const promotedRlsPolicyIds = new Set<string>();
|
|
127
|
+
const additions: Change[] = collectInvalidatedRlsPolicyReplacements({
|
|
128
|
+
changes,
|
|
129
|
+
mainCatalog,
|
|
130
|
+
branchCatalog,
|
|
131
|
+
createdIds,
|
|
132
|
+
droppedIds,
|
|
133
|
+
promotedRlsPolicyIds,
|
|
134
|
+
});
|
|
135
|
+
|
|
110
136
|
// Procedure stableIds are signature-qualified
|
|
111
137
|
// (`procedure:schema.name(argtypes)`), so a function whose parameter types
|
|
112
138
|
// change has different ids in `createdIds` and `droppedIds` and would not
|
|
@@ -150,7 +176,7 @@ export function expandReplaceDependencies({
|
|
|
150
176
|
}
|
|
151
177
|
}
|
|
152
178
|
|
|
153
|
-
if (replaceRoots.size === 0) {
|
|
179
|
+
if (replaceRoots.size === 0 && additions.length === 0) {
|
|
154
180
|
return {
|
|
155
181
|
changes,
|
|
156
182
|
replacedTableIds: new Set<string>(),
|
|
@@ -168,7 +194,6 @@ export function expandReplaceDependencies({
|
|
|
168
194
|
list.add(dep.dependent_stable_id);
|
|
169
195
|
}
|
|
170
196
|
|
|
171
|
-
const additions: Change[] = [];
|
|
172
197
|
const visitedTargets = new Set<string>();
|
|
173
198
|
const visitedRefs = new Set<string>(replaceRoots);
|
|
174
199
|
const queue: string[] = [...replaceRoots];
|
|
@@ -236,10 +261,14 @@ export function expandReplaceDependencies({
|
|
|
236
261
|
const replacementChanges = buildReplaceChanges(resolved, {
|
|
237
262
|
addDrop,
|
|
238
263
|
addCreate,
|
|
264
|
+
diffContext,
|
|
239
265
|
});
|
|
240
266
|
if (!replacementChanges) continue;
|
|
241
267
|
|
|
242
268
|
additions.push(...replacementChanges);
|
|
269
|
+
if (resolved.kind === "rls_policy") {
|
|
270
|
+
promotedRlsPolicyIds.add(targetId);
|
|
271
|
+
}
|
|
243
272
|
|
|
244
273
|
// If we added a DropTable(T) for an existing table, mark T so any
|
|
245
274
|
// pre-existing object-scope AlterTable*(T) changes get dropped below —
|
|
@@ -264,11 +293,90 @@ export function expandReplaceDependencies({
|
|
|
264
293
|
}
|
|
265
294
|
|
|
266
295
|
return {
|
|
267
|
-
changes: [
|
|
296
|
+
changes: [
|
|
297
|
+
...removeSupersededRlsPolicyAlters(changes, promotedRlsPolicyIds),
|
|
298
|
+
...additions,
|
|
299
|
+
],
|
|
268
300
|
replacedTableIds: tablesReplacedByExpansion,
|
|
269
301
|
};
|
|
270
302
|
}
|
|
271
303
|
|
|
304
|
+
function collectInvalidatedRlsPolicyReplacements({
|
|
305
|
+
changes,
|
|
306
|
+
mainCatalog,
|
|
307
|
+
branchCatalog,
|
|
308
|
+
createdIds,
|
|
309
|
+
droppedIds,
|
|
310
|
+
promotedRlsPolicyIds,
|
|
311
|
+
}: {
|
|
312
|
+
changes: Change[];
|
|
313
|
+
mainCatalog: Catalog;
|
|
314
|
+
branchCatalog: Catalog;
|
|
315
|
+
createdIds: Set<string>;
|
|
316
|
+
droppedIds: Set<string>;
|
|
317
|
+
promotedRlsPolicyIds: Set<string>;
|
|
318
|
+
}): Change[] {
|
|
319
|
+
// In-place rewrites report stable ids through `invalidates`: the referenced
|
|
320
|
+
// object keeps its identity, but dependents bound to the old definition must
|
|
321
|
+
// be torn down first. RLS policy expressions are tracked in pg_depend, so use
|
|
322
|
+
// those catalog edges to promote only policies that depend on an invalidated
|
|
323
|
+
// id, without coupling this expansion pass to a concrete table-change class.
|
|
324
|
+
const invalidatedIds = new Set<string>();
|
|
325
|
+
for (const change of changes) {
|
|
326
|
+
for (const invalidatedId of change.invalidates) {
|
|
327
|
+
invalidatedIds.add(invalidatedId);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (invalidatedIds.size === 0) return [];
|
|
331
|
+
|
|
332
|
+
const replacements: Change[] = [];
|
|
333
|
+
for (const dep of mainCatalog.depends) {
|
|
334
|
+
if (!invalidatedIds.has(dep.referenced_stable_id)) continue;
|
|
335
|
+
|
|
336
|
+
const targetId = normalizeDependentId(dep.dependent_stable_id);
|
|
337
|
+
if (!targetId?.startsWith("rlsPolicy:")) continue;
|
|
338
|
+
if (promotedRlsPolicyIds.has(targetId)) continue;
|
|
339
|
+
if (createdIds.has(targetId) && droppedIds.has(targetId)) continue;
|
|
340
|
+
|
|
341
|
+
const resolved = resolveObjectForStableId(
|
|
342
|
+
targetId,
|
|
343
|
+
mainCatalog,
|
|
344
|
+
branchCatalog,
|
|
345
|
+
);
|
|
346
|
+
if (!resolved || resolved.kind !== "rls_policy") continue;
|
|
347
|
+
|
|
348
|
+
const addDrop = !droppedIds.has(targetId);
|
|
349
|
+
const addCreate = !createdIds.has(targetId);
|
|
350
|
+
const replacementChanges = buildReplaceChanges(resolved, {
|
|
351
|
+
addDrop,
|
|
352
|
+
addCreate,
|
|
353
|
+
});
|
|
354
|
+
if (!replacementChanges) continue;
|
|
355
|
+
|
|
356
|
+
replacements.push(...replacementChanges);
|
|
357
|
+
promotedRlsPolicyIds.add(targetId);
|
|
358
|
+
for (const change of replacementChanges) {
|
|
359
|
+
for (const id of change.creates ?? []) createdIds.add(id);
|
|
360
|
+
for (const id of change.drops ?? []) droppedIds.add(id);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return replacements;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function removeSupersededRlsPolicyAlters(
|
|
368
|
+
changes: Change[],
|
|
369
|
+
promotedRlsPolicyIds: ReadonlySet<string>,
|
|
370
|
+
): Change[] {
|
|
371
|
+
if (promotedRlsPolicyIds.size === 0) return changes;
|
|
372
|
+
return changes.filter((change) => {
|
|
373
|
+
if (change.objectType !== "rls_policy" || change.operation !== "alter") {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return !promotedRlsPolicyIds.has(change.policy.stableId);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
272
380
|
function isOwnedSequenceColumnDependency(
|
|
273
381
|
referencedId: string,
|
|
274
382
|
dependentId: string,
|
|
@@ -395,6 +503,12 @@ function resolveObjectForStableId(
|
|
|
395
503
|
return main && branch ? { kind: "procedure", main, branch } : null;
|
|
396
504
|
}
|
|
397
505
|
|
|
506
|
+
if (stableId.startsWith("rlsPolicy:")) {
|
|
507
|
+
const main = mainCatalog.rlsPolicies[stableId];
|
|
508
|
+
const branch = branchCatalog.rlsPolicies[stableId];
|
|
509
|
+
return main && branch ? { kind: "rls_policy", main, branch } : null;
|
|
510
|
+
}
|
|
511
|
+
|
|
398
512
|
if (stableId.startsWith("domain:")) {
|
|
399
513
|
const main = mainCatalog.domains[stableId];
|
|
400
514
|
const branch = branchCatalog.domains[stableId];
|
|
@@ -430,9 +544,16 @@ function resolveObjectForStableId(
|
|
|
430
544
|
|
|
431
545
|
function buildReplaceChanges(
|
|
432
546
|
resolved: ResolvedObject,
|
|
433
|
-
options: {
|
|
547
|
+
options: {
|
|
548
|
+
addDrop: boolean;
|
|
549
|
+
addCreate: boolean;
|
|
550
|
+
diffContext?: Pick<
|
|
551
|
+
ObjectDiffContext,
|
|
552
|
+
"version" | "currentUser" | "defaultPrivilegeState"
|
|
553
|
+
>;
|
|
554
|
+
},
|
|
434
555
|
): Change[] | null {
|
|
435
|
-
const { addDrop, addCreate } = options;
|
|
556
|
+
const { addDrop, addCreate, diffContext } = options;
|
|
436
557
|
|
|
437
558
|
if (!addDrop && !addCreate) return null;
|
|
438
559
|
|
|
@@ -471,7 +592,9 @@ function buildReplaceChanges(
|
|
|
471
592
|
case "view":
|
|
472
593
|
return [
|
|
473
594
|
...(addDrop ? [new DropView({ view: resolved.main })] : []),
|
|
474
|
-
...(addCreate
|
|
595
|
+
...(addCreate
|
|
596
|
+
? buildCreateViewReplacementChanges(resolved.branch, diffContext)
|
|
597
|
+
: []),
|
|
475
598
|
];
|
|
476
599
|
case "materialized_view":
|
|
477
600
|
return [
|
|
@@ -479,7 +602,13 @@ function buildReplaceChanges(
|
|
|
479
602
|
? [new DropMaterializedView({ materializedView: resolved.main })]
|
|
480
603
|
: []),
|
|
481
604
|
...(addCreate
|
|
482
|
-
?
|
|
605
|
+
? diffContext
|
|
606
|
+
? buildCreateMaterializedViewChanges(diffContext, resolved.branch)
|
|
607
|
+
: [
|
|
608
|
+
new CreateMaterializedView({
|
|
609
|
+
materializedView: resolved.branch,
|
|
610
|
+
}),
|
|
611
|
+
]
|
|
483
612
|
: []),
|
|
484
613
|
];
|
|
485
614
|
case "index":
|
|
@@ -519,6 +648,18 @@ function buildReplaceChanges(
|
|
|
519
648
|
? [new CreateProcedure({ procedure: resolved.branch })]
|
|
520
649
|
: []),
|
|
521
650
|
];
|
|
651
|
+
case "rls_policy":
|
|
652
|
+
return [
|
|
653
|
+
...(addDrop ? [new DropRlsPolicy({ policy: resolved.main })] : []),
|
|
654
|
+
...(addCreate
|
|
655
|
+
? [
|
|
656
|
+
new CreateRlsPolicy({ policy: resolved.branch }),
|
|
657
|
+
...(resolved.branch.comment !== null
|
|
658
|
+
? [new CreateCommentOnRlsPolicy({ policy: resolved.branch })]
|
|
659
|
+
: []),
|
|
660
|
+
]
|
|
661
|
+
: []),
|
|
662
|
+
];
|
|
522
663
|
case "enum":
|
|
523
664
|
return [
|
|
524
665
|
...(addDrop ? [new DropEnum({ enum: resolved.main })] : []),
|
|
@@ -547,3 +688,20 @@ function buildReplaceChanges(
|
|
|
547
688
|
return null;
|
|
548
689
|
}
|
|
549
690
|
}
|
|
691
|
+
|
|
692
|
+
function buildCreateViewReplacementChanges(
|
|
693
|
+
view: Catalog["views"][string],
|
|
694
|
+
diffContext:
|
|
695
|
+
| Pick<
|
|
696
|
+
ObjectDiffContext,
|
|
697
|
+
"version" | "currentUser" | "defaultPrivilegeState"
|
|
698
|
+
>
|
|
699
|
+
| undefined,
|
|
700
|
+
): Change[] {
|
|
701
|
+
// Dependency-closure replacements synthesize a create without going through
|
|
702
|
+
// `diffViews`, so replay the same owner/comment/security-label/ACL metadata
|
|
703
|
+
// that a normal non-alterable view replacement would emit.
|
|
704
|
+
return diffContext
|
|
705
|
+
? buildCreateViewChanges(diffContext, view)
|
|
706
|
+
: [new CreateView({ view })];
|
|
707
|
+
}
|
|
@@ -51,6 +51,21 @@ export abstract class BaseChange {
|
|
|
51
51
|
return [];
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Stable identifiers this change invalidates in place.
|
|
56
|
+
*
|
|
57
|
+
* Unlike `drops`, the object keeps its identity. This is an ordering-only
|
|
58
|
+
* signal for mutations that rewrite an existing object in a way that requires
|
|
59
|
+
* dependents bound to the old definition to be dropped before the mutation
|
|
60
|
+
* and rebuilt afterward.
|
|
61
|
+
*
|
|
62
|
+
* Defaults to an empty array. Override in subclasses that invalidate
|
|
63
|
+
* dependents without dropping the object.
|
|
64
|
+
*/
|
|
65
|
+
get invalidates(): string[] {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
54
69
|
/**
|
|
55
70
|
* Stable identifiers this change requires to exist beforehand.
|
|
56
71
|
*
|
|
@@ -106,7 +106,7 @@ describe.concurrent("materialized-view.diff", () => {
|
|
|
106
106
|
expect(changes[0]).toBeInstanceOf(AlterMaterializedViewChangeOwner);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
test("drop + create on non-alterable change", () => {
|
|
109
|
+
test("drop + create with metadata on non-alterable change", () => {
|
|
110
110
|
const main = new MaterializedView(base);
|
|
111
111
|
const branch = new MaterializedView({ ...base, definition: "select 2" });
|
|
112
112
|
const changes = diffMaterializedViews(
|
|
@@ -114,9 +114,10 @@ describe.concurrent("materialized-view.diff", () => {
|
|
|
114
114
|
{ [main.stableId]: main },
|
|
115
115
|
{ [branch.stableId]: branch },
|
|
116
116
|
);
|
|
117
|
-
expect(changes).toHaveLength(
|
|
117
|
+
expect(changes).toHaveLength(3);
|
|
118
118
|
expect(changes[0]).toBeInstanceOf(DropMaterializedView);
|
|
119
119
|
expect(changes[1]).toBeInstanceOf(CreateMaterializedView);
|
|
120
|
+
expect(changes[2]).toBeInstanceOf(AlterMaterializedViewChangeOwner);
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
test("alter storage parameters: set and reset", () => {
|
|
@@ -30,117 +30,126 @@ import {
|
|
|
30
30
|
import type { MaterializedViewChange } from "./changes/materialized-view.types.ts";
|
|
31
31
|
import type { MaterializedView } from "./materialized-view.model.ts";
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
* Diff two sets of materialized views from main and branch catalogs.
|
|
35
|
-
*
|
|
36
|
-
* @param ctx - Context containing version, currentUser, and defaultPrivilegeState
|
|
37
|
-
* @param main - The materialized views in the main catalog.
|
|
38
|
-
* @param branch - The materialized views in the branch catalog.
|
|
39
|
-
* @returns A list of changes to apply to main to make it match branch.
|
|
40
|
-
*/
|
|
41
|
-
export function diffMaterializedViews(
|
|
33
|
+
export function buildCreateMaterializedViewChanges(
|
|
42
34
|
ctx: Pick<
|
|
43
35
|
ObjectDiffContext,
|
|
44
36
|
"version" | "currentUser" | "defaultPrivilegeState"
|
|
45
37
|
>,
|
|
46
|
-
|
|
47
|
-
branch: Record<string, MaterializedView>,
|
|
38
|
+
mv: MaterializedView,
|
|
48
39
|
): MaterializedViewChange[] {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
const changes: MaterializedViewChange[] = [
|
|
41
|
+
new CreateMaterializedView({
|
|
42
|
+
materializedView: mv,
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
// OWNER: If the materialized view should be owned by someone other than the current user,
|
|
47
|
+
// emit ALTER MATERIALIZED VIEW ... OWNER TO after creation
|
|
48
|
+
if (mv.owner !== ctx.currentUser) {
|
|
55
49
|
changes.push(
|
|
56
|
-
new
|
|
50
|
+
new AlterMaterializedViewChangeOwner({
|
|
57
51
|
materializedView: mv,
|
|
52
|
+
owner: mv.owner,
|
|
58
53
|
}),
|
|
59
54
|
);
|
|
55
|
+
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
// Materialized view comment on creation
|
|
58
|
+
if (mv.comment !== null) {
|
|
59
|
+
changes.push(
|
|
60
|
+
new CreateCommentOnMaterializedView({
|
|
61
|
+
materializedView: mv,
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
// Column comments on creation
|
|
66
|
+
for (const col of mv.columns) {
|
|
67
|
+
if (col.comment !== null) {
|
|
64
68
|
changes.push(
|
|
65
|
-
new
|
|
69
|
+
new CreateCommentOnMaterializedViewColumn({
|
|
66
70
|
materializedView: mv,
|
|
67
|
-
|
|
71
|
+
column: col,
|
|
68
72
|
}),
|
|
69
73
|
);
|
|
70
74
|
}
|
|
75
|
+
}
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
// Security labels on the matview itself (columns of matviews are not
|
|
78
|
+
// supported targets of SECURITY LABEL, so we only label the relation).
|
|
79
|
+
for (const label of mv.security_labels) {
|
|
80
|
+
changes.push(
|
|
81
|
+
new CreateSecurityLabelOnMaterializedView({
|
|
82
|
+
materializedView: mv,
|
|
83
|
+
securityLabel: label,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
// PRIVILEGES: For created objects, compare against default privileges state
|
|
89
|
+
// The migration script will run ALTER DEFAULT PRIVILEGES before CREATE (via constraint spec),
|
|
90
|
+
// so objects are created with the default privileges state in effect.
|
|
91
|
+
// We compare default privileges against desired privileges to generate REVOKE/GRANT statements
|
|
92
|
+
// needed to reach the final desired state.
|
|
93
|
+
const effectiveDefaults = ctx.defaultPrivilegeState.getEffectiveDefaults(
|
|
94
|
+
ctx.currentUser,
|
|
95
|
+
"materialized_view",
|
|
96
|
+
mv.schema ?? "",
|
|
97
|
+
);
|
|
98
|
+
const creatorFilteredDefaults =
|
|
99
|
+
mv.owner !== ctx.currentUser
|
|
100
|
+
? effectiveDefaults.filter((p) => p.grantee !== ctx.currentUser)
|
|
101
|
+
: effectiveDefaults;
|
|
102
|
+
const desiredPrivileges = mv.privileges;
|
|
103
|
+
// Filter out owner privileges - owner always has ALL privileges implicitly
|
|
104
|
+
// and shouldn't be compared. Use the materialized view owner as the reference.
|
|
105
|
+
const privilegeResults = diffPrivileges(
|
|
106
|
+
creatorFilteredDefaults,
|
|
107
|
+
desiredPrivileges,
|
|
108
|
+
mv.owner,
|
|
109
|
+
);
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
changes.push(
|
|
112
|
+
...(emitColumnPrivilegeChanges(
|
|
113
|
+
privilegeResults,
|
|
114
|
+
mv,
|
|
115
|
+
mv,
|
|
116
|
+
"materializedView",
|
|
117
|
+
{
|
|
118
|
+
Grant: GrantMaterializedViewPrivileges,
|
|
119
|
+
Revoke: RevokeMaterializedViewPrivileges,
|
|
120
|
+
RevokeGrantOption: RevokeGrantOptionMaterializedViewPrivileges,
|
|
121
|
+
},
|
|
122
|
+
effectiveDefaults,
|
|
123
|
+
ctx.version,
|
|
124
|
+
) as MaterializedViewChange[]),
|
|
125
|
+
);
|
|
106
126
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
mv.owner,
|
|
128
|
-
);
|
|
127
|
+
return changes;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Diff two sets of materialized views from main and branch catalogs.
|
|
132
|
+
*
|
|
133
|
+
* @param ctx - Context containing version, currentUser, and defaultPrivilegeState
|
|
134
|
+
* @param main - The materialized views in the main catalog.
|
|
135
|
+
* @param branch - The materialized views in the branch catalog.
|
|
136
|
+
* @returns A list of changes to apply to main to make it match branch.
|
|
137
|
+
*/
|
|
138
|
+
export function diffMaterializedViews(
|
|
139
|
+
ctx: Pick<
|
|
140
|
+
ObjectDiffContext,
|
|
141
|
+
"version" | "currentUser" | "defaultPrivilegeState"
|
|
142
|
+
>,
|
|
143
|
+
main: Record<string, MaterializedView>,
|
|
144
|
+
branch: Record<string, MaterializedView>,
|
|
145
|
+
): MaterializedViewChange[] {
|
|
146
|
+
const { created, dropped, altered } = diffObjects(main, branch);
|
|
129
147
|
|
|
148
|
+
const changes: MaterializedViewChange[] = [];
|
|
149
|
+
|
|
150
|
+
for (const materializedViewId of created) {
|
|
130
151
|
changes.push(
|
|
131
|
-
...(
|
|
132
|
-
privilegeResults,
|
|
133
|
-
mv,
|
|
134
|
-
mv,
|
|
135
|
-
"materializedView",
|
|
136
|
-
{
|
|
137
|
-
Grant: GrantMaterializedViewPrivileges,
|
|
138
|
-
Revoke: RevokeMaterializedViewPrivileges,
|
|
139
|
-
RevokeGrantOption: RevokeGrantOptionMaterializedViewPrivileges,
|
|
140
|
-
},
|
|
141
|
-
effectiveDefaults,
|
|
142
|
-
ctx.version,
|
|
143
|
-
) as MaterializedViewChange[]),
|
|
152
|
+
...buildCreateMaterializedViewChanges(ctx, branch[materializedViewId]),
|
|
144
153
|
);
|
|
145
154
|
}
|
|
146
155
|
|
|
@@ -180,9 +189,7 @@ export function diffMaterializedViews(
|
|
|
180
189
|
// Replace the entire materialized view (drop + create)
|
|
181
190
|
changes.push(
|
|
182
191
|
new DropMaterializedView({ materializedView: mainMaterializedView }),
|
|
183
|
-
|
|
184
|
-
materializedView: branchMaterializedView,
|
|
185
|
-
}),
|
|
192
|
+
...buildCreateMaterializedViewChanges(ctx, branchMaterializedView),
|
|
186
193
|
);
|
|
187
194
|
} else {
|
|
188
195
|
// Only alterable properties changed - check each one
|
|
@@ -651,6 +651,15 @@ export class AlterTableAlterColumnType extends AlterTableChange {
|
|
|
651
651
|
];
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
get invalidates() {
|
|
655
|
+
// ALTER COLUMN ... TYPE rewrites the column in place. The column keeps its
|
|
656
|
+
// identity, but anything bound to its old type (views, rules, etc.) must be
|
|
657
|
+
// dropped before the rewrite and rebuilt after, so report it as invalidated.
|
|
658
|
+
return [
|
|
659
|
+
stableId.column(this.table.schema, this.table.name, this.column.name),
|
|
660
|
+
];
|
|
661
|
+
}
|
|
662
|
+
|
|
654
663
|
serialize(_options?: SerializeOptions): string {
|
|
655
664
|
// previousColumn is optional so direct serializer tests/fixtures can keep
|
|
656
665
|
// emitting canonical ALTER TYPE SQL without forcing a USING expression.
|