Skip to content

Commit 51ace65

Browse files
fix(migration): permission group migration error (#4258)
1 parent c2529c3 commit 51ace65

1 file changed

Lines changed: 49 additions & 14 deletions

File tree

packages/db/migrations/0194_careless_pete_wisdom.sql

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
-- permission_group_member table also gains a denormalized workspace_id column
66
-- so the database can enforce the "one group per user per workspace"
77
-- invariant with a composite unique index.
8+
--
9+
-- Every statement below is written to be idempotent so the migration can be
10+
-- safely re-run after a partial failure.
811

912
-- 0. Backfill workspace -> organization links for grandfathered workspaces whose
1013
-- billed account user is the sole owner of exactly one organization. This is a
@@ -29,13 +32,42 @@ WHERE w."organization_id" IS NULL
2932
AND w."billed_account_user_id" = owner_orgs."user_id";--> statement-breakpoint
3033

3134
-- 1. Add workspace_id columns as nullable so existing rows can coexist during the data migration.
32-
ALTER TABLE "permission_group" ADD COLUMN "workspace_id" text;--> statement-breakpoint
33-
ALTER TABLE "permission_group" ADD CONSTRAINT "permission_group_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
34-
ALTER TABLE "permission_group_member" ADD COLUMN "workspace_id" text;--> statement-breakpoint
35-
ALTER TABLE "permission_group_member" ADD CONSTRAINT "permission_group_member_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
35+
ALTER TABLE "permission_group" ADD COLUMN IF NOT EXISTS "workspace_id" text;--> statement-breakpoint
36+
DO $$ BEGIN
37+
IF NOT EXISTS (
38+
SELECT 1 FROM information_schema.table_constraints
39+
WHERE constraint_name = 'permission_group_workspace_id_workspace_id_fk'
40+
AND table_name = 'permission_group'
41+
) THEN
42+
ALTER TABLE "permission_group"
43+
ADD CONSTRAINT "permission_group_workspace_id_workspace_id_fk"
44+
FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id")
45+
ON DELETE cascade ON UPDATE no action;
46+
END IF;
47+
END $$;--> statement-breakpoint
48+
49+
ALTER TABLE "permission_group_member" ADD COLUMN IF NOT EXISTS "workspace_id" text;--> statement-breakpoint
50+
DO $$ BEGIN
51+
IF NOT EXISTS (
52+
SELECT 1 FROM information_schema.table_constraints
53+
WHERE constraint_name = 'permission_group_member_workspace_id_workspace_id_fk'
54+
AND table_name = 'permission_group_member'
55+
) THEN
56+
ALTER TABLE "permission_group_member"
57+
ADD CONSTRAINT "permission_group_member_workspace_id_workspace_id_fk"
58+
FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id")
59+
ON DELETE cascade ON UPDATE no action;
60+
END IF;
61+
END $$;--> statement-breakpoint
62+
63+
-- 1b. Relax NOT NULL on permission_group.organization_id before the data migration.
64+
-- Step 3 inserts clone rows with organization_id = NULL to mark them as the new
65+
-- workspace-scoped shape. This DROP NOT NULL is a no-op if already nullable.
66+
ALTER TABLE "permission_group" ALTER COLUMN "organization_id" DROP NOT NULL;--> statement-breakpoint
3667

3768
-- 2. Materialize a plan of (source permission group, target workspace, new clone id)
3869
-- so we can insert the clone rows AND the member rows with stable references.
70+
-- Temp tables are always fresh per transaction, so this is naturally idempotent.
3971
CREATE TEMP TABLE "__permission_group_clone_plan" (
4072
"source_id" text NOT NULL,
4173
"cloned_id" text NOT NULL,
@@ -49,6 +81,8 @@ JOIN "workspace" w ON w."organization_id" = pg."organization_id"
4981
WHERE pg."organization_id" IS NOT NULL;--> statement-breakpoint
5082

5183
-- 3. Create the workspace-scoped clone rows using the planned ids.
84+
-- Naturally idempotent: after a successful prior run there are no org-scoped
85+
-- rows left, so the clone plan is empty and this INSERT is a no-op.
5286
INSERT INTO "permission_group" (
5387
"id",
5488
"workspace_id",
@@ -101,6 +135,7 @@ WHERE EXISTS (
101135
);--> statement-breakpoint
102136

103137
-- 5. Delete legacy org-scoped rows now that clones exist.
138+
-- Idempotent: no rows match on a re-run.
104139
DELETE FROM "permission_group_member"
105140
WHERE "permission_group_id" IN (
106141
SELECT "id" FROM "permission_group" WHERE "organization_id" IS NOT NULL
@@ -109,20 +144,20 @@ WHERE "permission_group_id" IN (
109144
DELETE FROM "permission_group" WHERE "organization_id" IS NOT NULL;--> statement-breakpoint
110145

111146
-- 6. Enforce NOT NULL on both workspace_id columns now that every surviving row has one.
147+
-- SET NOT NULL is a no-op if already NOT NULL.
112148
ALTER TABLE "permission_group" ALTER COLUMN "workspace_id" SET NOT NULL;--> statement-breakpoint
113149
ALTER TABLE "permission_group_member" ALTER COLUMN "workspace_id" SET NOT NULL;--> statement-breakpoint
114150

115151
-- 7. Drop legacy structures and swap indexes.
116-
ALTER TABLE "permission_group" DROP CONSTRAINT "permission_group_organization_id_organization_id_fk";--> statement-breakpoint
117-
DROP INDEX "permission_group_org_name_unique";--> statement-breakpoint
118-
DROP INDEX "permission_group_org_auto_add_unique";--> statement-breakpoint
119-
DROP INDEX "permission_group_member_user_id_unique";--> statement-breakpoint
120-
ALTER TABLE "permission_group" DROP COLUMN "organization_id";--> statement-breakpoint
121-
CREATE UNIQUE INDEX "permission_group_workspace_name_unique" ON "permission_group" USING btree ("workspace_id","name");--> statement-breakpoint
122-
CREATE UNIQUE INDEX "permission_group_workspace_auto_add_unique" ON "permission_group" USING btree ("workspace_id") WHERE auto_add_new_members = true;--> statement-breakpoint
123-
CREATE UNIQUE INDEX "permission_group_member_group_user_unique" ON "permission_group_member" USING btree ("permission_group_id","user_id");--> statement-breakpoint
124-
CREATE UNIQUE INDEX "permission_group_member_workspace_user_unique" ON "permission_group_member" USING btree ("workspace_id","user_id");--> statement-breakpoint
152+
ALTER TABLE "permission_group" DROP CONSTRAINT IF EXISTS "permission_group_organization_id_organization_id_fk";--> statement-breakpoint
153+
DROP INDEX IF EXISTS "permission_group_org_name_unique";--> statement-breakpoint
154+
DROP INDEX IF EXISTS "permission_group_org_auto_add_unique";--> statement-breakpoint
155+
DROP INDEX IF EXISTS "permission_group_member_user_id_unique";--> statement-breakpoint
156+
ALTER TABLE "permission_group" DROP COLUMN IF EXISTS "organization_id";--> statement-breakpoint
157+
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_workspace_name_unique" ON "permission_group" USING btree ("workspace_id","name");--> statement-breakpoint
158+
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_workspace_auto_add_unique" ON "permission_group" USING btree ("workspace_id") WHERE auto_add_new_members = true;--> statement-breakpoint
159+
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_member_group_user_unique" ON "permission_group_member" USING btree ("permission_group_id","user_id");--> statement-breakpoint
160+
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_member_workspace_user_unique" ON "permission_group_member" USING btree ("workspace_id","user_id");--> statement-breakpoint
125161

126162
-- 8. Sweep any residual dead config keys from pre-existing workspace-scoped rows (if any).
127163
UPDATE "permission_group" SET "config" = ("config" - 'hideEnvironmentTab' - 'hideTemplates') WHERE "config" ? 'hideEnvironmentTab' OR "config" ? 'hideTemplates';
128-

0 commit comments

Comments
 (0)