Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cool-windows-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@supabase/pg-delta": patch
---

Order dependent view drops before column type rewrites.
Comment thread
avallete marked this conversation as resolved.
Outdated
4 changes: 4 additions & 0 deletions packages/pg-delta/src/core/catalog.diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ export function diffCatalogs(
}
let filteredChanges = changes.filter((change) => {
if (change.operation === "alter" && change.scope === "privilege") {
if ((change.creates ?? []).length > 0) {
Comment thread
avallete marked this conversation as resolved.
Outdated
return true;
}
Comment thread
avallete marked this conversation as resolved.
Outdated
return !droppedObjectStableIds.has(getPrivilegeTargetStableId(change));
}
return true;
Expand All @@ -238,6 +241,7 @@ export function diffCatalogs(
changes: filteredChanges,
mainCatalog: main,
branchCatalog: branch,
diffContext,
});
filteredChanges = normalizePostDiffChanges({
changes: expandedDependencies.changes,
Expand Down
104 changes: 100 additions & 4 deletions packages/pg-delta/src/core/expand-replace-dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import type { Catalog } from "./catalog.model.ts";
import type { Change } from "./change.types.ts";
import {
diffPrivileges,
emitColumnPrivilegeChanges,
} from "./objects/base.privilege-diff.ts";
import type { ObjectDiffContext } from "./objects/diff-context.ts";
import { CreateDomain } from "./objects/domain/changes/domain.create.ts";
import { DropDomain } from "./objects/domain/changes/domain.drop.ts";
import { CreateIndex } from "./objects/index/changes/index.create.ts";
import { DropIndex } from "./objects/index/changes/index.drop.ts";
import { CreateMaterializedView } from "./objects/materialized-view/changes/materialized-view.create.ts";
import { DropMaterializedView } from "./objects/materialized-view/changes/materialized-view.drop.ts";
import { buildCreateMaterializedViewChanges } from "./objects/materialized-view/materialized-view.diff.ts";
import { CreateProcedure } from "./objects/procedure/changes/procedure.create.ts";
import { DropProcedure } from "./objects/procedure/changes/procedure.drop.ts";
import { CreateCommentOnRlsPolicy } from "./objects/rls-policy/changes/rls-policy.comment.ts";
Expand All @@ -22,8 +28,16 @@ import { DropEnum } from "./objects/type/enum/changes/enum.drop.ts";
import { CreateRange } from "./objects/type/range/changes/range.create.ts";
import { DropRange } from "./objects/type/range/changes/range.drop.ts";
import { stableId } from "./objects/utils.ts";
import { AlterViewChangeOwner } from "./objects/view/changes/view.alter.ts";
import { CreateCommentOnView } from "./objects/view/changes/view.comment.ts";
import { CreateView } from "./objects/view/changes/view.create.ts";
import { DropView } from "./objects/view/changes/view.drop.ts";
import {
GrantViewPrivileges,
RevokeGrantOptionViewPrivileges,
RevokeViewPrivileges,
} from "./objects/view/changes/view.privilege.ts";
import { CreateSecurityLabelOnView } from "./objects/view/changes/view.security-label.ts";

type ResolvedObject =
| {
Expand Down Expand Up @@ -95,10 +109,15 @@ export function expandReplaceDependencies({
changes,
mainCatalog,
branchCatalog,
diffContext,
}: {
changes: Change[];
mainCatalog: Catalog;
branchCatalog: Catalog;
diffContext?: Pick<
ObjectDiffContext,
"version" | "currentUser" | "defaultPrivilegeState"
>;
}): ExpandReplaceDependenciesResult {
const createdIds = new Set<string>();
const droppedIds = new Set<string>();
Expand Down Expand Up @@ -244,6 +263,7 @@ export function expandReplaceDependencies({
const replacementChanges = buildReplaceChanges(resolved, {
addDrop,
addCreate,
diffContext,
});
if (!replacementChanges) continue;

Expand Down Expand Up @@ -444,9 +464,16 @@ function resolveObjectForStableId(

function buildReplaceChanges(
resolved: ResolvedObject,
options: { addDrop: boolean; addCreate: boolean },
options: {
addDrop: boolean;
addCreate: boolean;
diffContext?: Pick<
ObjectDiffContext,
"version" | "currentUser" | "defaultPrivilegeState"
>;
},
): Change[] | null {
const { addDrop, addCreate } = options;
const { addDrop, addCreate, diffContext } = options;

if (!addDrop && !addCreate) return null;

Expand Down Expand Up @@ -485,15 +512,23 @@ function buildReplaceChanges(
case "view":
return [
...(addDrop ? [new DropView({ view: resolved.main })] : []),
...(addCreate ? [new CreateView({ view: resolved.branch })] : []),
...(addCreate
? buildCreateViewReplacementChanges(resolved.branch, diffContext)
: []),
];
case "materialized_view":
return [
...(addDrop
? [new DropMaterializedView({ materializedView: resolved.main })]
: []),
...(addCreate
? [new CreateMaterializedView({ materializedView: resolved.branch })]
? diffContext
? buildCreateMaterializedViewChanges(diffContext, resolved.branch)
: [
new CreateMaterializedView({
materializedView: resolved.branch,
}),
]
: []),
];
case "index":
Expand Down Expand Up @@ -573,3 +608,64 @@ function buildReplaceChanges(
return null;
}
}

function buildCreateViewReplacementChanges(
Comment thread
avallete marked this conversation as resolved.
Comment thread
avallete marked this conversation as resolved.
view: Catalog["views"][string],
diffContext:
| Pick<
ObjectDiffContext,
"version" | "currentUser" | "defaultPrivilegeState"
>
| undefined,
): Change[] {
const changes: Change[] = [new CreateView({ view })];
if (!diffContext) return changes;

if (view.owner !== diffContext.currentUser) {
changes.push(new AlterViewChangeOwner({ view, owner: view.owner }));
}

if (view.comment !== null) {
changes.push(new CreateCommentOnView({ view }));
}

for (const securityLabel of view.security_labels) {
changes.push(new CreateSecurityLabelOnView({ view, securityLabel }));
}

const effectiveDefaults =
diffContext.defaultPrivilegeState.getEffectiveDefaults(
diffContext.currentUser,
"view",
view.schema ?? "",
);
const creatorFilteredDefaults =
view.owner !== diffContext.currentUser
? effectiveDefaults.filter(
(privilege) => privilege.grantee !== diffContext.currentUser,
)
: effectiveDefaults;
const privilegeResults = diffPrivileges(
creatorFilteredDefaults,
view.privileges,
view.owner,
);

changes.push(
...(emitColumnPrivilegeChanges(
privilegeResults,
view,
view,
"view",
{
Grant: GrantViewPrivileges,
Revoke: RevokeViewPrivileges,
RevokeGrantOption: RevokeGrantOptionViewPrivileges,
},
effectiveDefaults,
diffContext.version,
) as Change[]),
);

return changes;
}
15 changes: 15 additions & 0 deletions packages/pg-delta/src/core/objects/base.change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ export abstract class BaseChange {
return [];
}

/**
* Stable identifiers this change invalidates in place.
*
* Unlike `drops`, the object keeps its identity. This is an ordering-only
* signal for mutations that rewrite an existing object in a way that requires
* dependents bound to the old definition to be dropped before the mutation
* and rebuilt afterward.
*
* Defaults to an empty array. Override in subclasses that invalidate
* dependents without dropping the object.
*/
get invalidates(): string[] {
return [];
}

/**
* Stable identifiers this change requires to exist beforehand.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,18 @@ describe.concurrent("materialized-view.diff", () => {
expect(changes[0]).toBeInstanceOf(AlterMaterializedViewChangeOwner);
});

test("drop + create on non-alterable change", () => {
test("drop + create with metadata on non-alterable change", () => {
const main = new MaterializedView(base);
const branch = new MaterializedView({ ...base, definition: "select 2" });
const changes = diffMaterializedViews(
testContext,
{ [main.stableId]: main },
{ [branch.stableId]: branch },
);
expect(changes).toHaveLength(2);
expect(changes).toHaveLength(3);
expect(changes[0]).toBeInstanceOf(DropMaterializedView);
expect(changes[1]).toBeInstanceOf(CreateMaterializedView);
expect(changes[2]).toBeInstanceOf(AlterMaterializedViewChangeOwner);
});

test("alter storage parameters: set and reset", () => {
Expand Down
Loading
Loading