From f5828780fd6a8be070b2933ebd41ee6dff98a9e1 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 24 Jun 2026 12:38:44 +0200 Subject: [PATCH] fix(backend): resolve folder_labels search_path on non-public (PG_SCHEMA) schemas (#9758) * fix(backend): strip search_path=public from folder_labels migrations for non-public schema The folder-labels migrations (20260610151334_folder_labels, 20260614075900_dedup_folder_labels) define `folder_labels(...)` with `SET search_path = public` in their `CREATE FUNCTION` bodies. When Windmill runs in a non-public schema (PG_SCHEMA), PostgreSQL validates the function body against the `public` schema, where the `folder` table lacks the new `labels` column, failing with `column "labels" does not exist`. Add both migrations to OVERRIDDEN_MIGRATIONS, stripping the `SET search_path = public` clause so the function inherits the current search_path (which resolves the correct schema). Same regression and fix pattern as PR #5400. Fixes WIN-2093 Co-Authored-By: Claude Opus 4.8 (1M context) * fix(backend): pin folder_labels search_path FROM CURRENT instead of stripping it Keep the SECURITY DEFINER injection hardening while resolving the correct schema on non-public (PG_SCHEMA) installs: FROM CURRENT snapshots the migration connection's search_path at function creation time (public on normal installs, the custom schema otherwise) instead of dropping the pin and inheriting the caller's search_path at call time. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(backend): repair migration to re-pin folder_labels search_path on applied instances Instances that already applied the folder-labels migrations with the hardcoded SET search_path = public have a folder_labels function pinned to public. On a non-public (PG_SCHEMA) schema that reads the wrong folder table at runtime; the OVERRIDDEN_MIGRATIONS fix only helps instances that have not applied them yet. Add a CREATE OR REPLACE ... SET search_path FROM CURRENT migration that re-pins the function to the migration connection's schema. No-op on public installs (re-pins to public) and idempotent on already-correct ones. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- ..._repair_folder_labels_search_path.down.sql | 3 +++ ...00_repair_folder_labels_search_path.up.sql | 22 +++++++++++++++++++ backend/windmill-api/src/db.rs | 6 +++++ 3 files changed, 31 insertions(+) create mode 100644 backend/migrations/20260624103600_repair_folder_labels_search_path.down.sql create mode 100644 backend/migrations/20260624103600_repair_folder_labels_search_path.up.sql diff --git a/backend/migrations/20260624103600_repair_folder_labels_search_path.down.sql b/backend/migrations/20260624103600_repair_folder_labels_search_path.down.sql new file mode 100644 index 0000000000..3d532de9cb --- /dev/null +++ b/backend/migrations/20260624103600_repair_folder_labels_search_path.down.sql @@ -0,0 +1,3 @@ +-- No-op: this migration only re-pins the function's search_path. Reverting would +-- mean restoring the hardcoded `SET search_path = public`, which is the very bug +-- this repairs, so there is nothing to undo. diff --git a/backend/migrations/20260624103600_repair_folder_labels_search_path.up.sql b/backend/migrations/20260624103600_repair_folder_labels_search_path.up.sql new file mode 100644 index 0000000000..f8e448891f --- /dev/null +++ b/backend/migrations/20260624103600_repair_folder_labels_search_path.up.sql @@ -0,0 +1,22 @@ +-- Repair instances that already applied the folder-labels migrations while the +-- function hardcoded `SET search_path = public`. On a non-public schema (PG_SCHEMA) +-- the function was pinned to `public`, so at runtime it read the wrong `folder` +-- table (or a stray public.folder) instead of the workspace's real one. +-- +-- `FROM CURRENT` snapshots the migration connection's search_path (the actual +-- Windmill schema) into the function, keeping the SECURITY DEFINER injection +-- hardening. On public-schema installs this re-pins to `public`, i.e. a no-op. +-- Idempotent: redefining with the same body is harmless on already-correct installs. +CREATE OR REPLACE FUNCTION folder_labels(w_id text, item_path text) RETURNS text[] +LANGUAGE sql STABLE SECURITY DEFINER SET search_path FROM CURRENT AS $$ + SELECT ( + SELECT array_agg(l ORDER BY first_ord) + FROM ( + SELECT u.l, min(u.ord) AS first_ord + FROM unnest(f.labels) WITH ORDINALITY AS u(l, ord) + GROUP BY u.l + ) deduped + ) + FROM folder f + WHERE f.workspace_id = w_id AND item_path LIKE 'f/%' AND f.name = split_part(item_path, '/', 2) +$$; diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index 7b8d468da8..df6c8cc6b5 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -84,6 +84,12 @@ lazy_static::lazy_static! { (20260228000000, include_str!( "../../migrations/20260228000000_v2_job_completed_failure_index.up.sql" ).replace("CREATE INDEX", "CREATE INDEX CONCURRENTLY")), + (20260610151334, include_str!( + "../../migrations/20260610151334_folder_labels.up.sql" + ).replace("SET search_path = public", "SET search_path FROM CURRENT").to_string()), + (20260614075900, include_str!( + "../../migrations/20260614075900_dedup_folder_labels.up.sql" + ).replace("SET search_path = public", "SET search_path FROM CURRENT").to_string()), ].into_iter().collect(); }