You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(db): make the folder migration replay-safe below its embedded COMMIT
The file ends in CONCURRENTLY index builds under a COMMIT, so a failure
there replays the whole file from the top — but the statements above were
not idempotent, which would have wedged the deploy on retry. Every one is
now idempotent, matching 0250_workspace_forking.sql: CREATE TYPE and every
ADD CONSTRAINT in a duplicate_object handler, IF NOT EXISTS on tables,
columns and indexes, IF EXISTS on the constraint drops, OR REPLACE on the
function, and a drop-then-create for the trigger. Both backfills already
carried ON CONFLICT (id) DO NOTHING.
-- workflow.folderId in schema.ts. The invariant is enforced in the application layer
31
37
-- on both the old and new code paths meanwhile.
32
38
-- migration-safe: strictly loosens the column, no cross-deploy write can be rejected by removing this constraint; see contract-pending marker on workflow.folderId in schema.ts
33
-
ALTERTABLE"workflow" DROP CONSTRAINT"workflow_folder_id_workflow_folder_id_fk";
39
+
ALTERTABLE"workflow" DROP CONSTRAINTIF EXISTS "workflow_folder_id_workflow_folder_id_fk";
34
40
--> statement-breakpoint
35
41
-- Same reasoning as the workflow.folder_id drop above.
36
42
-- migration-safe: strictly loosens the column, no cross-deploy write can be rejected by removing this constraint; see contract-pending marker on workspaceFiles.folderId in schema.ts
37
-
ALTERTABLE"workspace_files" DROP CONSTRAINT"workspace_files_folder_id_workspace_file_folders_id_fk";
43
+
ALTERTABLE"workspace_files" DROP CONSTRAINTIF EXISTS "workspace_files_folder_id_workspace_file_folders_id_fk";
ALTERTABLE"folder" ADD CONSTRAINT"folder_user_id_user_id_fk"FOREIGN KEY ("user_id") REFERENCES"public"."user"("id") ON DELETE cascadeONUPDATE no action;--> statement-breakpoint
42
-
ALTERTABLE"folder" ADD CONSTRAINT"folder_workspace_id_workspace_id_fk"FOREIGN KEY ("workspace_id") REFERENCES"public"."workspace"("id") ON DELETE cascadeONUPDATE no action;--> statement-breakpoint
43
-
ALTERTABLE"pinned_item" ADD CONSTRAINT"pinned_item_user_id_user_id_fk"FOREIGN KEY ("user_id") REFERENCES"public"."user"("id") ON DELETE cascadeONUPDATE no action;--> statement-breakpoint
44
-
ALTERTABLE"pinned_item" ADD CONSTRAINT"pinned_item_workspace_id_workspace_id_fk"FOREIGN KEY ("workspace_id") REFERENCES"public"."workspace"("id") ON DELETE cascadeONUPDATE no action;--> statement-breakpoint
45
-
CREATEINDEX "folder_user_idx" ON"folder" USING btree ("user_id");--> statement-breakpoint
46
-
CREATEINDEX "folder_workspace_resource_parent_idx" ON"folder" USING btree ("workspace_id","resource_type","parent_id");--> statement-breakpoint
47
-
CREATEINDEX "folder_parent_sort_idx" ON"folder" USING btree ("parent_id","sort_order");--> statement-breakpoint
48
-
CREATEINDEX "folder_deleted_at_idx" ON"folder" USING btree ("deleted_at");--> statement-breakpoint
49
-
CREATEINDEX "folder_workspace_deleted_partial_idx" ON"folder" USING btree ("workspace_id","deleted_at") WHERE"folder"."deleted_at"IS NOT NULL;--> statement-breakpoint
50
-
CREATEUNIQUE INDEX "folder_workspace_resource_parent_name_active_unique" ON"folder" USING btree ("workspace_id","resource_type",coalesce("parent_id", ''),"name") WHERE"folder"."deleted_at" IS NULL;--> statement-breakpoint
51
-
CREATEINDEX "pinned_item_user_workspace_idx" ON"pinned_item" USING btree ("user_id","workspace_id");--> statement-breakpoint
52
-
CREATEINDEX "pinned_item_resource_idx" ON"pinned_item" USING btree ("resource_type","resource_id");--> statement-breakpoint
53
-
CREATEUNIQUE INDEX "pinned_item_user_resource_unique" ON"pinned_item" USING btree ("user_id","resource_type","resource_id");--> statement-breakpoint
45
+
ALTERTABLE"knowledge_base" ADD COLUMN IF NOT EXISTS "folder_id"text;--> statement-breakpoint
46
+
ALTERTABLE"user_table_definitions" ADD COLUMN IF NOT EXISTS "folder_id"text;--> statement-breakpoint
47
+
DO $$ BEGIN
48
+
ALTERTABLE"folder" ADD CONSTRAINT"folder_user_id_user_id_fk"FOREIGN KEY ("user_id") REFERENCES"public"."user"("id") ON DELETE cascadeONUPDATE no action;
49
+
EXCEPTION WHEN duplicate_object THEN null;
50
+
END $$;--> statement-breakpoint
51
+
DO $$ BEGIN
52
+
ALTERTABLE"folder" ADD CONSTRAINT"folder_workspace_id_workspace_id_fk"FOREIGN KEY ("workspace_id") REFERENCES"public"."workspace"("id") ON DELETE cascadeONUPDATE no action;
53
+
EXCEPTION WHEN duplicate_object THEN null;
54
+
END $$;--> statement-breakpoint
55
+
DO $$ BEGIN
56
+
ALTERTABLE"pinned_item" ADD CONSTRAINT"pinned_item_user_id_user_id_fk"FOREIGN KEY ("user_id") REFERENCES"public"."user"("id") ON DELETE cascadeONUPDATE no action;
57
+
EXCEPTION WHEN duplicate_object THEN null;
58
+
END $$;--> statement-breakpoint
59
+
DO $$ BEGIN
60
+
ALTERTABLE"pinned_item" ADD CONSTRAINT"pinned_item_workspace_id_workspace_id_fk"FOREIGN KEY ("workspace_id") REFERENCES"public"."workspace"("id") ON DELETE cascadeONUPDATE no action;
61
+
EXCEPTION WHEN duplicate_object THEN null;
62
+
END $$;--> statement-breakpoint
63
+
CREATEINDEXIF NOT EXISTS "folder_user_idx"ON"folder" USING btree ("user_id");--> statement-breakpoint
64
+
CREATEINDEXIF NOT EXISTS "folder_workspace_resource_parent_idx"ON"folder" USING btree ("workspace_id","resource_type","parent_id");--> statement-breakpoint
65
+
CREATEINDEXIF NOT EXISTS "folder_parent_sort_idx"ON"folder" USING btree ("parent_id","sort_order");--> statement-breakpoint
66
+
CREATEINDEXIF NOT EXISTS "folder_deleted_at_idx"ON"folder" USING btree ("deleted_at");--> statement-breakpoint
67
+
CREATEINDEXIF NOT EXISTS "folder_workspace_deleted_partial_idx"ON"folder" USING btree ("workspace_id","deleted_at") WHERE"folder"."deleted_at"IS NOT NULL;--> statement-breakpoint
68
+
CREATEUNIQUE INDEXIF NOT EXISTS "folder_workspace_resource_parent_name_active_unique"ON"folder" USING btree ("workspace_id","resource_type",coalesce("parent_id", ''),"name") WHERE"folder"."deleted_at" IS NULL;--> statement-breakpoint
69
+
CREATEINDEXIF NOT EXISTS "pinned_item_user_workspace_idx"ON"pinned_item" USING btree ("user_id","workspace_id");--> statement-breakpoint
70
+
CREATEINDEXIF NOT EXISTS "pinned_item_resource_idx"ON"pinned_item" USING btree ("resource_type","resource_id");--> statement-breakpoint
71
+
CREATEUNIQUE INDEXIF NOT EXISTS "pinned_item_user_resource_unique"ON"pinned_item" USING btree ("user_id","resource_type","resource_id");--> statement-breakpoint
54
72
-- A folder may only be parented by a folder of the same resourceType in the same
55
73
-- workspace. Neither invariant is expressible as a plain FK, so it is enforced here as
56
74
-- defense-in-depth behind the application-layer check. BEFORE INSERT OR UPDATE covers the
@@ -61,7 +79,7 @@ CREATE UNIQUE INDEX "pinned_item_user_resource_unique" ON "pinned_item" USING bt
61
79
-- whose parent has not been inserted yet finds no parent row, and passes rather than
62
80
-- raising. The real referential check is the FK, which Postgres evaluates as an
63
81
-- AFTER-ROW trigger at end of statement, once every row is present.
64
-
CREATEFUNCTION "folder_parent_resource_type_match"() RETURNS trigger AS $$
82
+
CREATE OR REPLACEFUNCTION "folder_parent_resource_type_match"() RETURNS trigger AS $$
65
83
DECLARE
66
84
parent_resource_type "folder_resource_type";
67
85
parent_workspace_id text;
@@ -82,6 +100,7 @@ BEGIN
82
100
END;
83
101
$$ LANGUAGE plpgsql;
84
102
--> statement-breakpoint
103
+
DROPTRIGGER IF EXISTS "folder_parent_resource_type_match"ON"folder";--> statement-breakpoint
85
104
CREATETRIGGER "folder_parent_resource_type_match"
86
105
BEFORE INSERT ORUPDATEON"folder"
87
106
FOR EACH ROW EXECUTE FUNCTION "folder_parent_resource_type_match"();
@@ -187,16 +206,25 @@ ON CONFLICT (id) DO NOTHING;
187
206
-- single INSERT...SELECT carrying children and parents in arbitrary order would in fact
188
207
-- satisfy it — but creating the constraint here removes any dependence on that subtlety,
189
208
-- costs nothing on a table this size, and makes the backfill obviously correct on review.
190
-
ALTERTABLE"folder" ADD CONSTRAINT"folder_parent_id_folder_id_fk"FOREIGN KEY ("parent_id") REFERENCES"public"."folder"("id") ON DELETEsetnullONUPDATE no action;--> statement-breakpoint
209
+
DO $$ BEGIN
210
+
ALTERTABLE"folder" ADD CONSTRAINT"folder_parent_id_folder_id_fk"FOREIGN KEY ("parent_id") REFERENCES"public"."folder"("id") ON DELETEsetnullONUPDATE no action;
211
+
EXCEPTION WHEN duplicate_object THEN null;
212
+
END $$;--> statement-breakpoint
191
213
-- knowledge_base.folder_id / user_table_definitions.folder_id were added earlier in this
192
214
-- migration and are all-NULL, and no deployed code reads or writes them yet — so adding
193
215
-- their FK here (unlike the workflow/workspace_files drops above) carries no cross-deploy
194
216
-- write-compatibility risk. NOT VALID + an immediate VALIDATE avoids the full-table lock a
195
217
-- plain ADD CONSTRAINT takes; validating an all-NULL column is instant either way, but this
196
218
-- matches the established pattern (see migrations/0243_kb_workspace_cascade.sql).
197
-
ALTERTABLE"knowledge_base" ADD CONSTRAINT"knowledge_base_folder_id_folder_id_fk"FOREIGN KEY ("folder_id") REFERENCES"public"."folder"("id") ON DELETEsetnullONUPDATE no action NOT VALID;--> statement-breakpoint
219
+
DO $$ BEGIN
220
+
ALTERTABLE"knowledge_base" ADD CONSTRAINT"knowledge_base_folder_id_folder_id_fk"FOREIGN KEY ("folder_id") REFERENCES"public"."folder"("id") ON DELETEsetnullONUPDATE no action NOT VALID;
ALTERTABLE"user_table_definitions" ADD CONSTRAINT"user_table_definitions_folder_id_folder_id_fk"FOREIGN KEY ("folder_id") REFERENCES"public"."folder"("id") ON DELETEsetnullONUPDATE no action NOT VALID;--> statement-breakpoint
224
+
DO $$ BEGIN
225
+
ALTERTABLE"user_table_definitions" ADD CONSTRAINT"user_table_definitions_folder_id_folder_id_fk"FOREIGN KEY ("folder_id") REFERENCES"public"."folder"("id") ON DELETEsetnullONUPDATE no action NOT VALID;
0 commit comments