v20260703-0010-Fix-pg_dump-ACL-minimization-for-PROPERTY-.patch
text/x-patch
Filename: v20260703-0010-Fix-pg_dump-ACL-minimization-for-PROPERTY-.patch
Type: text/x-patch
Part: 0
Patch
Format: format-patch
Series: patch v20260703-0010
Subject: Fix pg_dump ACL minimization for PROPERTY GRAPH.
| File | + | − |
|---|---|---|
| src/backend/catalog/information_schema.sql | 1 | 1 |
| src/backend/utils/adt/acl.c | 3 | 0 |
| src/bin/pg_dump/pg_dump.c | 34 | 3 |
From 43a27e7a278d7b8a23bb7967c60aec5a226a3ebb Mon Sep 17 00:00:00 2001
From: Noah Misch <noah@leadboat.com>
Date: Wed, 1 Jul 2026 11:59:33 +0530
Subject: [PATCH v20260703 10/13] Fix pg_dump ACL minimization for PROPERTY
GRAPH.
Adding a GRANT caused pg_dump to emit a useless REVOKE + GRANT of owner
privileges, as seen in a dump of the regression database:
REVOKE ALL ON PROPERTY GRAPH graph_rls_schema.cabinet FROM nm;
GRANT ALL ON PROPERTY GRAPH graph_rls_schema.cabinet TO nm;
GRANT ALL ON PROPERTY GRAPH graph_rls_schema.cabinet TO PUBLIC;
For normal dumps, this has no functional consequences. For --no-owner
restores, the extra statements may fail or locate unrelated users of the
destination cluster.
The problem was pg_dump assuming NULL relacl implies acldefault('r'),
the default for TABLE. Fix by teaching acldefault() to retrieve the
PROPERTY GRAPH default ACL. So pg_dump can still dump from 19beta1, use
acldefault('g') for v20+ only. For v19, use a hard-coded snapshot of
the v19 default.
information_schema.pg_property_graph_privileges also misused
acldefault('r'), but its "c.prtype IN ('SELECT')" predicate compensated
for it. Switch to the new acldefault('g') for clarity. Bump catversion
since a new view won't work with old binaries. Back-patch to v19, which
introduced PROPERTY GRAPH.
Reviewed-by: FIXME
Discussion: https://postgr.es/m/FIXME
Backpatch-through: 19
---
src/backend/catalog/information_schema.sql | 2 +-
src/backend/utils/adt/acl.c | 3 ++
src/bin/pg_dump/pg_dump.c | 37 ++++++++++++++++++++--
3 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 4f0e2492937..624d538a5c0 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -3328,7 +3328,7 @@ CREATE VIEW pg_property_graph_privileges AS
THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_grantable
FROM (
- SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class
+ SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('g', relowner)))).* FROM pg_class
) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable),
pg_namespace nc,
pg_authid u_grantor,
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 01caa12eca7..e2547d719ed 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -956,6 +956,9 @@ acldefault_sql(PG_FUNCTION_ARGS)
case 'c':
objtype = OBJECT_COLUMN;
break;
+ case 'g':
+ objtype = OBJECT_PROPGRAPH;
+ break;
case 'r':
objtype = OBJECT_TABLE;
break;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f67daf85911..4d660d14b4c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -7231,8 +7231,17 @@ getTables(Archive *fout, int *numTables)
"c.relhastriggers, c.relpersistence, "
"c.reloftype, "
"c.relacl, "
- "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
- " THEN 's'::\"char\" ELSE 'r'::\"char\" END, c.relowner) AS acldefault, "
+ "acldefault(CASE"
+ " WHEN c.relkind = " CppAsString2(RELKIND_PROPGRAPH));
+ /* 19beta1 didn't support acldefault('g'), so we'll fix that below */
+ appendPQExpBufferStr(query,
+ fout->remoteVersion >= 200000 ?
+ " THEN 'g'::\"char\"" :
+ " THEN NULL");
+ appendPQExpBufferStr(query,
+ " WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
+ " THEN 's'::\"char\""
+ " ELSE 'r'::\"char\" END, c.relowner) AS acldefault, "
"CASE WHEN c.relkind = " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN "
"(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
"ELSE 0 END AS foreignserver, "
@@ -7418,7 +7427,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].dobj.namespace =
findNamespace(atooid(PQgetvalue(res, i, i_relnamespace)));
tblinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_relacl));
- tblinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
+ /* acldefault computed below */
tblinfo[i].dacl.privtype = 0;
tblinfo[i].dacl.initprivs = NULL;
tblinfo[i].relkind = *(PQgetvalue(res, i, i_relkind));
@@ -7470,6 +7479,28 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0);
tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+ if (tblinfo[i].relkind == RELKIND_PROPGRAPH &&
+ !(fout->remoteVersion >= 200000))
+ {
+ PQExpBuffer aclarray = createPQExpBuffer();
+ PQExpBuffer aclitem = createPQExpBuffer();
+
+ /* Standard ACL as of v19 is {owner=r/owner} */
+ appendPQExpBufferChar(aclarray, '{');
+ quoteAclUserName(aclitem, tblinfo[i].rolname);
+ appendPQExpBufferStr(aclitem, "=r/");
+ quoteAclUserName(aclitem, tblinfo[i].rolname);
+ appendPGArray(aclarray, aclitem->data);
+ appendPQExpBufferChar(aclarray, '}');
+
+ tblinfo[i].dacl.acldefault = pstrdup(aclarray->data);
+
+ destroyPQExpBuffer(aclarray);
+ destroyPQExpBuffer(aclitem);
+ }
+ else
+ tblinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault));
+
/* other fields were zeroed above */
/*
--
2.34.1