From 27e835b677b979a6e054bd86848ecc1827ce11d6 Mon Sep 17 00:00:00 2001
From: Rui Zhao <zhaorui126@gmail.com>
Date: Mon, 27 Jul 2026 00:51:02 +0800
Subject: [PATCH] pg_dump: make the dangling-initprivs test exercise the fix

The test shipped with the dangling pg_init_privs fix passes with the fix
reverted: every object it creates keeps its default ACL, so pg_dump emits
no GRANT/REVOKE for those objects at all, and the unlike() assertions hold
vacuously.  The test also does not run at all on a cluster whose bootstrap
superuser name needs quoting, because the aclitem literals concatenate
current_user unquoted and aclitemin stops parsing the name at the dot.

Quote current_user with quote_ident(), give each object a non-default ACL
so that pg_dump actually emits privilege statements for it, widen the
catch-all assertion to cover REVOKE ... FROM "<oid>" as well as
GRANT ... TO "<oid>", and add coverage for the PUBLIC (grantee = 0) branch
of the filter.
---
 .../t/008_pg_dump_dangling_initprivs.pl       | 54 +++++++++++++++----
 1 file changed, 45 insertions(+), 9 deletions(-)

diff --git a/src/bin/pg_dump/t/008_pg_dump_dangling_initprivs.pl b/src/bin/pg_dump/t/008_pg_dump_dangling_initprivs.pl
index 1580a25638..e87f1a13e3 100644
--- a/src/bin/pg_dump/t/008_pg_dump_dangling_initprivs.pl
+++ b/src/bin/pg_dump/t/008_pg_dump_dangling_initprivs.pl
@@ -29,36 +29,40 @@ SET allow_system_table_mods = true;
 CREATE ROLE ghost_grantee;
 CREATE ROLE ghost_grantor;
 CREATE ROLE "007";
+CREATE ROLE regress_col_grantee;
 
 -- Case 1: dangling grantee (function)
 CREATE FUNCTION public.test_func_grantee() RETURNS int LANGUAGE sql AS 'SELECT 1';
+REVOKE ALL ON FUNCTION public.test_func_grantee() FROM PUBLIC;
 INSERT INTO pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
 SELECT p.oid,
        (SELECT oid FROM pg_class WHERE relname = 'pg_proc'),
        0, 'e',
-       ARRAY[('ghost_grantee=X/' || current_user)::aclitem]
+       ARRAY[('ghost_grantee=X/' || quote_ident(current_user))::aclitem]
 FROM pg_proc p
 WHERE p.proname = 'test_func_grantee'
   AND p.pronamespace = 'public'::regnamespace;
 
 -- Case 2: dangling grantor (function)
 CREATE FUNCTION public.test_func_grantor() RETURNS int LANGUAGE sql AS 'SELECT 2';
+REVOKE ALL ON FUNCTION public.test_func_grantor() FROM PUBLIC;
 INSERT INTO pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
 SELECT p.oid,
        (SELECT oid FROM pg_class WHERE relname = 'pg_proc'),
        0, 'e',
-       ARRAY[(current_user || '=X/ghost_grantor')::aclitem]
+       ARRAY[(quote_ident(current_user) || '=X/ghost_grantor')::aclitem]
 FROM pg_proc p
 WHERE p.proname = 'test_func_grantor'
   AND p.pronamespace = 'public'::regnamespace;
 
 -- Case 3: dangling column-level grantee (table)
 CREATE TABLE public.test_tbl (id int, secret text);
+GRANT SELECT (secret) ON public.test_tbl TO regress_col_grantee;
 INSERT INTO pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
 SELECT c.oid,
        (SELECT oid FROM pg_class WHERE relname = 'pg_class'),
        2, 'e',
-       ARRAY[('ghost_grantee=r/' || current_user)::aclitem]
+       ARRAY[('ghost_grantee=r/' || quote_ident(current_user))::aclitem]
 FROM pg_class c
 WHERE c.relname = 'test_tbl'
   AND c.relnamespace = 'public'::regnamespace;
@@ -70,7 +74,7 @@ INSERT INTO pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
 SELECT p.oid,
        (SELECT oid FROM pg_class WHERE relname = 'pg_proc'),
        0, 'e',
-       ARRAY[('ghost_grantee=X/' || current_user)::aclitem]
+       ARRAY[('ghost_grantee=X/' || quote_ident(current_user))::aclitem]
 FROM pg_proc p
 WHERE p.proname = 'test_func_revoke'
   AND p.pronamespace = 'public'::regnamespace;
@@ -79,6 +83,30 @@ WHERE p.proname = 'test_func_revoke'
 CREATE FUNCTION public.test_func_007() RETURNS int LANGUAGE sql AS 'SELECT 7';
 GRANT EXECUTE ON FUNCTION public.test_func_007() TO "007";
 
+-- Case 6: PUBLIC grant whose grantor is dangling (must be filtered)
+CREATE FUNCTION public.test_func_public_ghost() RETURNS int LANGUAGE sql AS 'SELECT 6';
+REVOKE ALL ON FUNCTION public.test_func_public_ghost() FROM PUBLIC;
+INSERT INTO pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
+SELECT p.oid,
+       (SELECT oid FROM pg_class WHERE relname = 'pg_proc'),
+       0, 'e',
+       ARRAY['=X/ghost_grantor'::aclitem]
+FROM pg_proc p
+WHERE p.proname = 'test_func_public_ghost'
+  AND p.pronamespace = 'public'::regnamespace;
+
+-- Case 7: PUBLIC grant with a valid grantor (must NOT be filtered)
+CREATE FUNCTION public.test_func_public_ok() RETURNS int LANGUAGE sql AS 'SELECT 7';
+REVOKE ALL ON FUNCTION public.test_func_public_ok() FROM PUBLIC;
+INSERT INTO pg_init_privs (objoid, classoid, objsubid, privtype, initprivs)
+SELECT p.oid,
+       (SELECT oid FROM pg_class WHERE relname = 'pg_proc'),
+       0, 'e',
+       ARRAY[('=X/' || quote_ident(current_user))::aclitem]
+FROM pg_proc p
+WHERE p.proname = 'test_func_public_ok'
+  AND p.pronamespace = 'public'::regnamespace;
+
 -- Now delete the ghost roles to create dangling OIDs
 DELETE FROM pg_authid WHERE rolname = 'ghost_grantee';
 DELETE FROM pg_authid WHERE rolname = 'ghost_grantor';
@@ -117,8 +145,8 @@ unlike($dump, qr/GRANT\b.*\btest_func_grantor/,
 # --- Case 3: column-level dangling ---
 like($dump, qr/CREATE TABLE public\.test_tbl/,
 	'case 3: table is present in dump');
-unlike($dump, qr/GRANT\b.*\btest_tbl\b.*secret/i,
-	'case 3: no column GRANT for dangling column-level entry');
+unlike($dump, qr/REVOKE\b.*\btest_tbl\b/,
+	'case 3: no column-level REVOKE naming a dangling OID');
 
 # --- Case 4: spurious REVOKE ---
 like($dump, qr/CREATE FUNCTION public\.test_func_revoke/,
@@ -133,8 +161,16 @@ like($dump, qr/GRANT\b.*\btest_func_007\b.*TO\s+"007"/,
 	'case 5: GRANT to valid all-digit role "007" is preserved');
 
 # --- General: no numeric OID as role name (other than the valid "007") ---
-# Match any GRANT ... TO "digits" where the digits are NOT "007".
-unlike($dump, qr/GRANT\b.*\bTO\s+"(?!007")[0-9]+"/,
-	'no GRANT with bare numeric OID as role name (other than valid "007")');
+# Match any GRANT/REVOKE naming a role as "digits", except the valid "007".
+unlike($dump, qr/^(?:GRANT|REVOKE)\b.*\b(?:TO|FROM)\s+"(?!007")[0-9]+"/m,
+	'no GRANT/REVOKE with bare numeric OID as role name (other than valid "007")');
+
+# --- Case 6: PUBLIC entry with dangling grantor is filtered ---
+unlike($dump, qr/GRANT\b.*\btest_func_public_ghost/,
+	'case 6: no GRANT for PUBLIC entry with dangling grantor');
+
+# --- Case 7: PUBLIC entry with valid grantor survives the filter ---
+like($dump, qr/GRANT\b.*\btest_func_public_ok/,
+	'case 7: PUBLIC entry with valid grantor is preserved');
 
 done_testing();
-- 
2.43.7

