From e46e036f79385801aeb8f5797356e977c93687de Mon Sep 17 00:00:00 2001 From: tianzhou Date: Fri, 13 Mar 2026 02:15:42 -0700 Subject: [PATCH 1/2] fix: render empty search_path as '' instead of "" (#354) PostgreSQL stores SET search_path = '' as search_path="" in proconfig. The extracted value is literal "" (two double-quote chars), which was rendered verbatim as invalid SQL. Now correctly renders as ''. Co-Authored-By: Claude Opus 4.6 --- internal/diff/function.go | 8 ++++++- .../issue_354_empty_search_path/diff.sql | 12 ++++++++++ .../issue_354_empty_search_path/new.sql | 17 ++++++++++++++ .../issue_354_empty_search_path/old.sql | 6 +++++ .../issue_354_empty_search_path/plan.json | 20 ++++++++++++++++ .../issue_354_empty_search_path/plan.sql | 12 ++++++++++ .../issue_354_empty_search_path/plan.txt | 23 +++++++++++++++++++ 7 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 testdata/diff/create_function/issue_354_empty_search_path/diff.sql create mode 100644 testdata/diff/create_function/issue_354_empty_search_path/new.sql create mode 100644 testdata/diff/create_function/issue_354_empty_search_path/old.sql create mode 100644 testdata/diff/create_function/issue_354_empty_search_path/plan.json create mode 100644 testdata/diff/create_function/issue_354_empty_search_path/plan.sql create mode 100644 testdata/diff/create_function/issue_354_empty_search_path/plan.txt diff --git a/internal/diff/function.go b/internal/diff/function.go index 4fa51ab6..1277f52a 100644 --- a/internal/diff/function.go +++ b/internal/diff/function.go @@ -246,7 +246,13 @@ func generateFunctionSQL(function *ir.Function, targetSchema string) string { // Note: Output without outer quotes to handle multi-schema paths correctly // e.g., "SET search_path = pg_catalog, public" not "SET search_path = 'pg_catalog, public'" if function.SearchPath != "" { - stmt.WriteString(fmt.Sprintf("\nSET search_path = %s", function.SearchPath)) + // PostgreSQL stores SET search_path = '' as search_path="" in proconfig. + // The extracted value is "" (two double-quote chars). Render as '' (single-quoted empty string). + if function.SearchPath == `""` { + stmt.WriteString("\nSET search_path = ''") + } else { + stmt.WriteString(fmt.Sprintf("\nSET search_path = %s", function.SearchPath)) + } } // Add the function body diff --git a/testdata/diff/create_function/issue_354_empty_search_path/diff.sql b/testdata/diff/create_function/issue_354_empty_search_path/diff.sql new file mode 100644 index 00000000..b01d1a00 --- /dev/null +++ b/testdata/diff/create_function/issue_354_empty_search_path/diff.sql @@ -0,0 +1,12 @@ +CREATE OR REPLACE FUNCTION create_hello( + p_title text +) +RETURNS void +LANGUAGE plpgsql +VOLATILE +SET search_path = '' +AS $$ +BEGIN + INSERT INTO test (title) VALUES (p_title); +END; +$$; diff --git a/testdata/diff/create_function/issue_354_empty_search_path/new.sql b/testdata/diff/create_function/issue_354_empty_search_path/new.sql new file mode 100644 index 00000000..15d5c695 --- /dev/null +++ b/testdata/diff/create_function/issue_354_empty_search_path/new.sql @@ -0,0 +1,17 @@ +CREATE TABLE public.test ( + id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT test_pkey PRIMARY KEY (id) +); + +CREATE OR REPLACE FUNCTION create_hello(p_title text) +RETURNS void +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = '' +AS $$ +BEGIN + INSERT INTO public.test (title) VALUES (p_title); +END; +$$; diff --git a/testdata/diff/create_function/issue_354_empty_search_path/old.sql b/testdata/diff/create_function/issue_354_empty_search_path/old.sql new file mode 100644 index 00000000..466001a5 --- /dev/null +++ b/testdata/diff/create_function/issue_354_empty_search_path/old.sql @@ -0,0 +1,6 @@ +CREATE TABLE public.test ( + id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT test_pkey PRIMARY KEY (id) +); diff --git a/testdata/diff/create_function/issue_354_empty_search_path/plan.json b/testdata/diff/create_function/issue_354_empty_search_path/plan.json new file mode 100644 index 00000000..674f3a36 --- /dev/null +++ b/testdata/diff/create_function/issue_354_empty_search_path/plan.json @@ -0,0 +1,20 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.7.4", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "742d612cedabf2d80d87df6c7d9fac8911e008b29ef8e15d16a32d2edf43ddd9" + }, + "groups": [ + { + "steps": [ + { + "sql": "CREATE OR REPLACE FUNCTION create_hello(\n p_title text\n)\nRETURNS void\nLANGUAGE plpgsql\nVOLATILE\nSET search_path = ''\nAS $$\nBEGIN\n INSERT INTO test (title) VALUES (p_title);\nEND;\n$$;", + "type": "function", + "operation": "create", + "path": "public.create_hello" + } + ] + } + ] +} diff --git a/testdata/diff/create_function/issue_354_empty_search_path/plan.sql b/testdata/diff/create_function/issue_354_empty_search_path/plan.sql new file mode 100644 index 00000000..b01d1a00 --- /dev/null +++ b/testdata/diff/create_function/issue_354_empty_search_path/plan.sql @@ -0,0 +1,12 @@ +CREATE OR REPLACE FUNCTION create_hello( + p_title text +) +RETURNS void +LANGUAGE plpgsql +VOLATILE +SET search_path = '' +AS $$ +BEGIN + INSERT INTO test (title) VALUES (p_title); +END; +$$; diff --git a/testdata/diff/create_function/issue_354_empty_search_path/plan.txt b/testdata/diff/create_function/issue_354_empty_search_path/plan.txt new file mode 100644 index 00000000..1c1a2b22 --- /dev/null +++ b/testdata/diff/create_function/issue_354_empty_search_path/plan.txt @@ -0,0 +1,23 @@ +Plan: 1 to add. + +Summary by type: + functions: 1 to add + +Functions: + + create_hello + +DDL to be executed: +-------------------------------------------------- + +CREATE OR REPLACE FUNCTION create_hello( + p_title text +) +RETURNS void +LANGUAGE plpgsql +VOLATILE +SET search_path = '' +AS $$ +BEGIN + INSERT INTO test (title) VALUES (p_title); +END; +$$; From d4bdf9a5e314e0058330e2c8276023efd6a1e3de Mon Sep 17 00:00:00 2001 From: tianzhou Date: Fri, 13 Mar 2026 02:23:15 -0700 Subject: [PATCH 2/2] fix: clarify search_path rendering comments per review feedback Co-Authored-By: Claude Opus 4.6 --- internal/diff/function.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/diff/function.go b/internal/diff/function.go index 1277f52a..d71cf46a 100644 --- a/internal/diff/function.go +++ b/internal/diff/function.go @@ -243,11 +243,12 @@ func generateFunctionSQL(function *ir.Function, targetSchema string) string { // Note: Don't output PARALLEL UNSAFE (it's the default) // Add SET search_path if specified - // Note: Output without outer quotes to handle multi-schema paths correctly - // e.g., "SET search_path = pg_catalog, public" not "SET search_path = 'pg_catalog, public'" + // Note: Multi-schema paths are output unquoted (e.g., "SET search_path = pg_catalog, public"), + // except for the empty search_path case which requires single quotes: SET search_path = '' if function.SearchPath != "" { // PostgreSQL stores SET search_path = '' as search_path="" in proconfig. // The extracted value is "" (two double-quote chars). Render as '' (single-quoted empty string). + // Only the whole-value empty case is handled; mixed paths (e.g. pg_catalog, "") are not expected. if function.SearchPath == `""` { stmt.WriteString("\nSET search_path = ''") } else {