From 3b0d572765a3a9d18752f46b504f66fd8dce9894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 03:18:57 +0300 Subject: [PATCH 1/5] fix(pgmq): resolve drop_queue overloading bug --- .../pgmq/after-create.sql | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql index 1581d93ca8..13369dc812 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql @@ -18,17 +18,22 @@ begin this update is backwards compatible with version 1.4.4 but should be removed once we're on physical backups everywhere */ --- Detach and delete the official function -if extversion = '1.4.4' then - alter extension pgmq drop function pgmq.drop_queue; - drop function pgmq.drop_queue; -else -- 1.5.1+ - alter extension pgmq drop function pgmq.drop_queue(TEXT); - drop function pgmq.drop_queue(TEXT); -end if; + -- detach and drop both historical drop_queue signatures (1.4.4 only ever + -- has (text, boolean); 1.5.0+ has both (text) and (text, boolean)) + begin + alter extension pgmq drop function pgmq.drop_queue(text); + exception when others then null; + end; + begin + alter extension pgmq drop function pgmq.drop_queue(text, boolean); + exception when others then null; + end; + + drop function if exists pgmq.drop_queue(text); + drop function if exists pgmq.drop_queue(text, boolean); -- Create and reattach the patched function -CREATE FUNCTION pgmq.drop_queue(queue_name TEXT) +CREATE FUNCTION pgmq.drop_queue(queue_name TEXT, partitioned BOOLEAN DEFAULT FALSE) RETURNS BOOLEAN AS $func$ DECLARE qtable TEXT := pgmq.format_table_name(queue_name, 'q'); @@ -36,7 +41,6 @@ DECLARE fq_qtable TEXT := 'pgmq.' || qtable; atable TEXT := pgmq.format_table_name(queue_name, 'a'); fq_atable TEXT := 'pgmq.' || atable; - partitioned BOOLEAN; BEGIN EXECUTE FORMAT( $QUERY$ @@ -140,11 +144,7 @@ BEGIN END; $func$ LANGUAGE plpgsql; -if extversion = '1.4.4' then - alter extension pgmq add function pgmq.drop_queue; -else -- 1.5.1+ - alter extension pgmq add function pgmq.drop_queue(TEXT); -end if; + alter extension pgmq add function pgmq.drop_queue(text, boolean); update pg_extension set extowner = 'postgres'::regrole where extname = 'pgmq'; From abb8b58b196ef788996b35061aaafb247a7f2014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 03:56:58 +0300 Subject: [PATCH 2/5] test(pgmq): add drop_queue overload regression test --- nix/ext/tests/pgmq-drop-queue-overload.nix | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 nix/ext/tests/pgmq-drop-queue-overload.nix diff --git a/nix/ext/tests/pgmq-drop-queue-overload.nix b/nix/ext/tests/pgmq-drop-queue-overload.nix new file mode 100644 index 0000000000..05a11606db --- /dev/null +++ b/nix/ext/tests/pgmq-drop-queue-overload.nix @@ -0,0 +1,85 @@ +{ self, pkgs }: +let + pname = "pgmq"; + inherit (pkgs) lib; + system = pkgs.pkgsLinux.stdenv.hostPlatform.system; + testLib = import ./lib.nix { inherit self pkgs; }; + + installedExtension = self.legacyPackages.${system}."psql_15".exts."${pname}"; + versions = installedExtension.versions; +in +pkgs.testers.runNixOSTest { + name = "pgmq-drop-queue-overload"; + nodes.server = + { ... }: + { + imports = [ + (testLib.makeSupabaseTestConfig { + majorVersion = "15"; + }) + ]; + }; + testScript = + { ... }: + let + versionList = lib.concatStringsSep ", " (map (v: ''"${v}"'') versions); + in + # python + '' + versions = [${versionList}] + + def sql(query): + return server.succeed( + "psql -U supabase_admin -d postgres -t -A -F',' -c \"" + query.replace('"', '\\"') + "\"" + ).strip() + + def drop_queue_overloads(): + # owned flag first: pg_get_function_identity_arguments() can itself + # contain a comma ("queue_name text, partitioned boolean"), so put the + # single-char flag first and split on the first comma only. + out = sql( + "select (d.objid is not null), pg_get_function_identity_arguments(p.oid) " + "from pg_proc p " + "left join pg_depend d on d.objid = p.oid and d.deptype = 'e' " + " and d.refobjid = (select oid from pg_extension where extname = 'pgmq') " + "where p.pronamespace = 'pgmq'::regnamespace and p.proname = 'drop_queue' " + "order by 2;" + ) + return [line.split(",", 1) for line in out.splitlines() if line] + + def check_callers(qname): + sql(f"select pgmq.create('{qname}_a'); select pgmq.drop_queue('{qname}_a');") + sql(f"select pgmq.create('{qname}_b'); select pgmq.drop_queue('{qname}_b', false);") + # WRONG flag on purpose (queue isn't partitioned) - must still + # succeed, safely ignored in favour of pgmq.meta + sql(f"select pgmq.create('{qname}_c'); select pgmq.drop_queue('{qname}_c', true);") + sql( + f"select pgmq.create('{qname}_d'); " + f"select pgmq.drop_queue(queue_name => '{qname}_d', partitioned => true);" + ) + + start_all() + server.wait_for_unit("supabase-db-init.service") + + for version in versions: + with subtest(f"install pgmq {version}"): + server.succeed("psql -U supabase_admin -d postgres -c 'DROP EXTENSION IF EXISTS pgmq;'") + server.succeed( + f"psql -U supabase_admin -d postgres -c \"CREATE EXTENSION pgmq WITH VERSION '{version}' CASCADE;\"" + ) + + overloads = drop_queue_overloads() + print(f"[{version}] drop_queue overloads: {overloads}") + assert len(overloads) == 1, ( + f"[{version}] expected exactly one drop_queue overload, got: {overloads}" + ) + assert overloads[0][0] == "t", ( + f"[{version}] drop_queue is not extension-owned: {overloads}" + ) + assert overloads[0][1] == "queue_name text, partitioned boolean", ( + f"[{version}] expected merged text,boolean signature, got: {overloads[0][1]}" + ) + + check_callers(f"q_{version.replace('.', '_')}") + ''; +} From 6e84f4778023ae2a1235c22654d7fc23d8b264f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 20:46:39 +0300 Subject: [PATCH 3/5] test(pgmq): minimize drop_queue overload test --- nix/ext/tests/pgmq-drop-queue-overload.nix | 54 ++++++++-------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/nix/ext/tests/pgmq-drop-queue-overload.nix b/nix/ext/tests/pgmq-drop-queue-overload.nix index 05a11606db..b8d9c2c982 100644 --- a/nix/ext/tests/pgmq-drop-queue-overload.nix +++ b/nix/ext/tests/pgmq-drop-queue-overload.nix @@ -6,7 +6,12 @@ let testLib = import ./lib.nix { inherit self pkgs; }; installedExtension = self.legacyPackages.${system}."psql_15".exts."${pname}"; - versions = installedExtension.versions; + # boundary versions only - the fix doesn't branch on extversion, so + # oldest/newest is enough to catch a regression + versions = lib.unique [ + (lib.head installedExtension.versions) + (lib.last installedExtension.versions) + ]; in pkgs.testers.runNixOSTest { name = "pgmq-drop-queue-overload"; @@ -30,33 +35,20 @@ pkgs.testers.runNixOSTest { def sql(query): return server.succeed( - "psql -U supabase_admin -d postgres -t -A -F',' -c \"" + query.replace('"', '\\"') + "\"" + "psql -U supabase_admin -d postgres -t -A -c \"" + query.replace('"', '\\"') + "\"" ).strip() - def drop_queue_overloads(): - # owned flag first: pg_get_function_identity_arguments() can itself - # contain a comma ("queue_name text, partitioned boolean"), so put the - # single-char flag first and split on the first comma only. - out = sql( - "select (d.objid is not null), pg_get_function_identity_arguments(p.oid) " + def assert_single_merged_overload(version): + ok = sql( + "select count(*) = 1 " + " and bool_and(d.objid is not null) " + " and bool_and(pg_get_function_identity_arguments(p.oid) = 'queue_name text, partitioned boolean') " "from pg_proc p " - "left join pg_depend d on d.objid = p.oid and d.deptype = 'e' " + "join pg_depend d on d.objid = p.oid and d.deptype = 'e' " " and d.refobjid = (select oid from pg_extension where extname = 'pgmq') " - "where p.pronamespace = 'pgmq'::regnamespace and p.proname = 'drop_queue' " - "order by 2;" - ) - return [line.split(",", 1) for line in out.splitlines() if line] - - def check_callers(qname): - sql(f"select pgmq.create('{qname}_a'); select pgmq.drop_queue('{qname}_a');") - sql(f"select pgmq.create('{qname}_b'); select pgmq.drop_queue('{qname}_b', false);") - # WRONG flag on purpose (queue isn't partitioned) - must still - # succeed, safely ignored in favour of pgmq.meta - sql(f"select pgmq.create('{qname}_c'); select pgmq.drop_queue('{qname}_c', true);") - sql( - f"select pgmq.create('{qname}_d'); " - f"select pgmq.drop_queue(queue_name => '{qname}_d', partitioned => true);" + "where p.pronamespace = 'pgmq'::regnamespace and p.proname = 'drop_queue';" ) + assert ok == "t", f"[{version}] expected exactly one merged, extension-owned drop_queue(text, boolean)" start_all() server.wait_for_unit("supabase-db-init.service") @@ -68,18 +60,10 @@ pkgs.testers.runNixOSTest { f"psql -U supabase_admin -d postgres -c \"CREATE EXTENSION pgmq WITH VERSION '{version}' CASCADE;\"" ) - overloads = drop_queue_overloads() - print(f"[{version}] drop_queue overloads: {overloads}") - assert len(overloads) == 1, ( - f"[{version}] expected exactly one drop_queue overload, got: {overloads}" - ) - assert overloads[0][0] == "t", ( - f"[{version}] drop_queue is not extension-owned: {overloads}" - ) - assert overloads[0][1] == "queue_name text, partitioned boolean", ( - f"[{version}] expected merged text,boolean signature, got: {overloads[0][1]}" - ) + assert_single_merged_overload(version) - check_callers(f"q_{version.replace('.', '_')}") + qname = f"q_{version.replace('.', '_')}" + sql(f"select pgmq.create('{qname}_a'); select pgmq.drop_queue('{qname}_a');") + sql(f"select pgmq.create('{qname}_b'); select pgmq.drop_queue('{qname}_b', false);") ''; } From d7c9f1b0dadbfc5e21d481d666a0b76fa70b1884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 22:21:53 +0300 Subject: [PATCH 4/5] test(pgmq): update expected-output snapshots for drop_queue fix Two drop_queue overloads collapse into one merged drop_queue(text, boolean), same as verified in the NixOS test. --- nix/tests/expected/pgmq.out | 3 +-- nix/tests/expected/z_15_ext_interface.out | 3 +-- nix/tests/expected/z_17_ext_interface.out | 3 +-- nix/tests/expected/z_multigres-17_ext_interface.out | 3 +-- nix/tests/expected/z_orioledb-17_ext_interface.out | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/nix/tests/expected/pgmq.out b/nix/tests/expected/pgmq.out index 5314e226c5..22ee12825b 100644 --- a/nix/tests/expected/pgmq.out +++ b/nix/tests/expected/pgmq.out @@ -175,7 +175,6 @@ order by pgmq | delete | postgres pgmq | detach_archive | postgres pgmq | drop_queue | postgres - pgmq | drop_queue | postgres pgmq | format_table_name | postgres pgmq | list_queues | postgres pgmq | metrics | postgres @@ -198,7 +197,7 @@ order by pgmq | send_batch | postgres pgmq | set_vt | postgres pgmq | validate_queue_name | postgres -(40 rows) +(39 rows) -- assert search_path is preserved after after-create script is run show search_path; diff --git a/nix/tests/expected/z_15_ext_interface.out b/nix/tests/expected/z_15_ext_interface.out index e53086bdec..19b80469b0 100644 --- a/nix/tests/expected/z_15_ext_interface.out +++ b/nix/tests/expected/z_15_ext_interface.out @@ -1379,7 +1379,6 @@ order by pgmq | pgmq | delete | queue_name text, msg_ids bigint[] | SETOF bigint pgmq | pgmq | detach_archive | queue_name text | void pgmq | pgmq | drop_queue | queue_name text, partitioned boolean | boolean - pgmq | pgmq | drop_queue | queue_name text | boolean pgmq | pgmq | format_table_name | queue_name text, prefix text | text pgmq | pgmq | list_queues | | SETOF pgmq.queue_record pgmq | pgmq | metrics | queue_name text | pgmq.metrics_result @@ -5307,7 +5306,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(5135 rows) +(5134 rows) /* diff --git a/nix/tests/expected/z_17_ext_interface.out b/nix/tests/expected/z_17_ext_interface.out index d589b8a6d1..ecda520934 100644 --- a/nix/tests/expected/z_17_ext_interface.out +++ b/nix/tests/expected/z_17_ext_interface.out @@ -1364,7 +1364,6 @@ order by pgmq | pgmq | delete | queue_name text, msg_ids bigint[] | SETOF bigint pgmq | pgmq | detach_archive | queue_name text | void pgmq | pgmq | drop_queue | queue_name text, partitioned boolean | boolean - pgmq | pgmq | drop_queue | queue_name text | boolean pgmq | pgmq | format_table_name | queue_name text, prefix text | text pgmq | pgmq | list_queues | | SETOF pgmq.queue_record pgmq | pgmq | metrics | queue_name text | pgmq.metrics_result @@ -4951,7 +4950,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(4792 rows) +(4791 rows) /* diff --git a/nix/tests/expected/z_multigres-17_ext_interface.out b/nix/tests/expected/z_multigres-17_ext_interface.out index 978d6adffe..4590968336 100644 --- a/nix/tests/expected/z_multigres-17_ext_interface.out +++ b/nix/tests/expected/z_multigres-17_ext_interface.out @@ -1364,7 +1364,6 @@ order by pgmq | pgmq | delete | queue_name text, msg_ids bigint[] | SETOF bigint pgmq | pgmq | detach_archive | queue_name text | void pgmq | pgmq | drop_queue | queue_name text, partitioned boolean | boolean - pgmq | pgmq | drop_queue | queue_name text | boolean pgmq | pgmq | format_table_name | queue_name text, prefix text | text pgmq | pgmq | list_queues | | SETOF pgmq.queue_record pgmq | pgmq | metrics | queue_name text | pgmq.metrics_result @@ -4814,7 +4813,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(4655 rows) +(4654 rows) /* diff --git a/nix/tests/expected/z_orioledb-17_ext_interface.out b/nix/tests/expected/z_orioledb-17_ext_interface.out index 0482d0b86b..123f339fdf 100644 --- a/nix/tests/expected/z_orioledb-17_ext_interface.out +++ b/nix/tests/expected/z_orioledb-17_ext_interface.out @@ -1416,7 +1416,6 @@ order by pgmq | pgmq | delete | queue_name text, msg_ids bigint[] | SETOF bigint pgmq | pgmq | detach_archive | queue_name text | void pgmq | pgmq | drop_queue | queue_name text, partitioned boolean | boolean - pgmq | pgmq | drop_queue | queue_name text | boolean pgmq | pgmq | format_table_name | queue_name text, prefix text | text pgmq | pgmq | list_queues | | SETOF pgmq.queue_record pgmq | pgmq | metrics | queue_name text | pgmq.metrics_result @@ -5003,7 +5002,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(4843 rows) +(4842 rows) /* From 0b5dce55cf0b0859066490392eb3e2eb0486a9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 02:14:31 +0300 Subject: [PATCH 5/5] test(pgmq): fix z_multigres-orioledb-17 snapshot (missed variant) --- nix/tests/expected/z_multigres-orioledb-17_ext_interface.out | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nix/tests/expected/z_multigres-orioledb-17_ext_interface.out b/nix/tests/expected/z_multigres-orioledb-17_ext_interface.out index c6d7b86175..328dcfae0d 100644 --- a/nix/tests/expected/z_multigres-orioledb-17_ext_interface.out +++ b/nix/tests/expected/z_multigres-orioledb-17_ext_interface.out @@ -1416,7 +1416,6 @@ order by pgmq | pgmq | delete | queue_name text, msg_ids bigint[] | SETOF bigint pgmq | pgmq | detach_archive | queue_name text | void pgmq | pgmq | drop_queue | queue_name text, partitioned boolean | boolean - pgmq | pgmq | drop_queue | queue_name text | boolean pgmq | pgmq | format_table_name | queue_name text, prefix text | text pgmq | pgmq | list_queues | | SETOF pgmq.queue_record pgmq | pgmq | metrics | queue_name text | pgmq.metrics_result @@ -4866,7 +4865,7 @@ order by xml2 | public | xpath_table | text, text, text, text, text | SETOF record xml2 | public | xslt_process | text, text | text xml2 | public | xslt_process | text, text, text | text -(4706 rows) +(4705 rows) /*