From 8a1adc8c1d750319c8afeeeaaa90782ef4a0a96e Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 20 Aug 2026 22:29:50 +0000 Subject: [PATCH] fix(db): make grant migration replay safe Co-Authored-By: Paperclip --- ...connection-grants-phase2-migration.test.ts | 11 ++++-- .../db/src/migrations/0227_third_devos.sql | 34 +++++++++++++------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/db/src/connection-grants-phase2-migration.test.ts b/packages/db/src/connection-grants-phase2-migration.test.ts index 837bb3aea34..d16b534bd74 100644 --- a/packages/db/src/connection-grants-phase2-migration.test.ts +++ b/packages/db/src/connection-grants-phase2-migration.test.ts @@ -20,8 +20,15 @@ describe("connection grants phase 2 migration", () => { }); it("adds credential policy and the grant audience table", () => { - expect(sql).toContain(`CREATE TABLE "connection_grant_members"`); - expect(sql).toContain(`ADD COLUMN "credential_policy" text DEFAULT 'shared' NOT NULL`); + expect(sql).toContain(`CREATE TABLE IF NOT EXISTS "connection_grant_members"`); + expect(sql).toContain(`ADD COLUMN IF NOT EXISTS "credential_policy" text DEFAULT 'shared' NOT NULL`); expect(sql).toContain(`'shared', 'per_user', 'per_user_with_fallback'`); }); + + it("is safe to replay when schema state is ahead of the migration journal", () => { + expect(sql).toContain(`DROP CONSTRAINT IF EXISTS "connection_grant_members_company_grant_fk"`); + expect(sql).toContain(`DROP CONSTRAINT IF EXISTS "tool_connections_credential_policy_check"`); + expect(sql).toContain(`CREATE UNIQUE INDEX IF NOT EXISTS "connection_grants_default_uq"`); + expect(sql).toContain(`IF NOT EXISTS (\n\t\tSELECT 1\n\t\tFROM pg_constraint`); + }); }); diff --git a/packages/db/src/migrations/0227_third_devos.sql b/packages/db/src/migrations/0227_third_devos.sql index b019cab7c0c..98b81da40a6 100644 --- a/packages/db/src/migrations/0227_third_devos.sql +++ b/packages/db/src/migrations/0227_third_devos.sql @@ -9,7 +9,7 @@ WHERE "entry"."profile_id" = "profile"."id" AND "profile"."metadata" ->> 'source' IN ('app_gallery_finish', 'tool_connection_install') AND "profile"."metadata" ->> 'connectionId' = "entry"."connection_id"::text; --> statement-breakpoint -CREATE TABLE "connection_grant_members" ( +CREATE TABLE IF NOT EXISTS "connection_grant_members" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "company_id" uuid NOT NULL, "grant_id" uuid NOT NULL, @@ -19,10 +19,14 @@ CREATE TABLE "connection_grant_members" ( CONSTRAINT "connection_grant_members_subject_type_check" CHECK ("connection_grant_members"."subject_type" in ('user')) ); --> statement-breakpoint -ALTER TABLE "connection_grants" DROP CONSTRAINT "connection_grants_kind_check";--> statement-breakpoint -ALTER TABLE "connection_grants" DROP CONSTRAINT "connection_grants_subject_check";--> statement-breakpoint -ALTER TABLE "connection_grants" DROP CONSTRAINT "connection_grants_default_check";--> statement-breakpoint -DROP INDEX "connection_grants_default_uq";--> statement-breakpoint +ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_company_grant_fk";--> statement-breakpoint +ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_grant_id_connection_grants_id_fk";--> statement-breakpoint +ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_company_id_companies_id_fk";--> statement-breakpoint +ALTER TABLE "connection_grants" DROP CONSTRAINT IF EXISTS "connection_grants_kind_check";--> statement-breakpoint +ALTER TABLE "connection_grants" DROP CONSTRAINT IF EXISTS "connection_grants_subject_check";--> statement-breakpoint +ALTER TABLE "connection_grants" DROP CONSTRAINT IF EXISTS "connection_grants_default_check";--> statement-breakpoint +ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_credential_policy_check";--> statement-breakpoint +DROP INDEX IF EXISTS "connection_grants_default_uq";--> statement-breakpoint UPDATE "connection_grants" SET "kind" = 'organization' WHERE "kind" = 'workspace';--> statement-breakpoint INSERT INTO "connection_grants" ( "company_id", "connection_id", "kind", "credential_secret_refs", "status", "is_default", @@ -36,13 +40,23 @@ WHERE NOT EXISTS ( SELECT 1 FROM "connection_grants" g WHERE g."connection_id" = c."id" AND g."is_default" = true );--> statement-breakpoint -ALTER TABLE "tool_connections" ADD COLUMN "credential_policy" text DEFAULT 'shared' NOT NULL;--> statement-breakpoint +ALTER TABLE "tool_connections" ADD COLUMN IF NOT EXISTS "credential_policy" text DEFAULT 'shared' NOT NULL;--> statement-breakpoint ALTER TABLE "connection_grant_members" ADD CONSTRAINT "connection_grant_members_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_company_id_uq" UNIQUE("company_id","id");--> statement-breakpoint +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'connection_grants_company_id_uq' + AND conrelid = 'connection_grants'::regclass + ) THEN + ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_company_id_uq" UNIQUE("company_id","id"); + END IF; +END $$;--> statement-breakpoint ALTER TABLE "connection_grant_members" ADD CONSTRAINT "connection_grant_members_company_grant_fk" FOREIGN KEY ("company_id","grant_id") REFERENCES "public"."connection_grants"("company_id","id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "connection_grant_members_company_subject_idx" ON "connection_grant_members" USING btree ("company_id","subject_type","subject_id");--> statement-breakpoint -CREATE UNIQUE INDEX "connection_grant_members_grant_subject_uq" ON "connection_grant_members" USING btree ("grant_id","subject_type","subject_id");--> statement-breakpoint -CREATE UNIQUE INDEX "connection_grants_default_uq" ON "connection_grants" USING btree ("connection_id") WHERE "connection_grants"."is_default" = true and "connection_grants"."kind" = 'organization';--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "connection_grant_members_company_subject_idx" ON "connection_grant_members" USING btree ("company_id","subject_type","subject_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "connection_grant_members_grant_subject_uq" ON "connection_grant_members" USING btree ("grant_id","subject_type","subject_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "connection_grants_default_uq" ON "connection_grants" USING btree ("connection_id") WHERE "connection_grants"."is_default" = true and "connection_grants"."kind" = 'organization';--> statement-breakpoint ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_kind_check" CHECK ("connection_grants"."kind" in ('organization', 'user'));--> statement-breakpoint ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_subject_check" CHECK (("connection_grants"."kind" = 'user' and "connection_grants"."subject_user_id" is not null) or ("connection_grants"."kind" = 'organization' and "connection_grants"."subject_user_id" is null));--> statement-breakpoint ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_default_check" CHECK ("connection_grants"."is_default" = false or "connection_grants"."kind" = 'organization');--> statement-breakpoint