pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

Started by Bossart, Nathanover 4 years ago8 messages
#1Bossart, Nathan
bossartn@amazon.com
1 attachment(s)

Hi hackers,

I received a report of missing privileges after an upgrade, and I
believe I've traced it back to pg_dump's handling of ALTER DEFAULT
PRIVILEGES IN SCHEMA. I did find a recent report [0]/messages/by-id/111621616618184@mail.yandex.ru that looks
related, but I didn't see any follow-ups on that thread. It looks
like the issue dates back to the introduction of pg_init_privs in v9.6
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=23f34fa

A simple reproduction of the issue is to run pg_dump after the
following command is run:

ALTER DEFAULT PRIVILEGES FOR ROLE nathan IN SCHEMA test GRANT EXECUTE ON FUNCTIONS TO PUBLIC;

pg_dump will emit this command for this ACL:

ALTER DEFAULT PRIVILEGES FOR ROLE nathan IN SCHEMA test REVOKE ALL ON FUNCTIONS FROM nathan;

The problem appears to be that pg_dump is comparing the entries in
pg_default_acl to the default ACL (i.e., acldefault()). This is fine
for "global" entries (i.e., entries with no schema specified), but it
doesn't work for "non-global" entries (i.e., entries with a schema
specified). This is because the default for a non-global entry is
actually an empty ACL. aclchk.c has the following comment:

/*
* The default for a global entry is the hard-wired default ACL for the
* particular object type. The default for non-global entries is an empty
* ACL. This must be so because global entries replace the hard-wired
* defaults, while others are added on.
*/

I've attached a quick hack that seems to fix this by adjusting the
pg_dump query to use NULL instead of acldefault() for non-global
entries. I'm posting this early in order to gather thoughts on the
approach and to make sure I'm not missing something obvious.

Nathan

[0]: /messages/by-id/111621616618184@mail.yandex.ru
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=23f34fa
[2]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=e2090d9

Attachments:

v1-0001-fix-pg_dump-handling-of-ALTER-DEFAULT-PRIVILEGES-.patchapplication/octet-stream; name=v1-0001-fix-pg_dump-handling-of-ALTER-DEFAULT-PRIVILEGES-.patchDownload
From dd73227a7722aaa672117077ac8d9e64e19d8a2a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Wed, 18 Aug 2021 16:30:35 +0000
Subject: [PATCH v1 1/1] fix pg_dump handling of ALTER DEFAULT PRIVILEGES IN
 SCHEMA

---
 src/bin/pg_dump/dumputils.c | 26 +++++++++++++++++++++-----
 src/bin/pg_dump/dumputils.h |  2 +-
 src/bin/pg_dump/pg_dump.c   | 24 ++++++++++++------------
 3 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index ea67e52a3f..2f2cc272c0 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -726,7 +726,7 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 				PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery,
 				const char *acl_column, const char *acl_owner,
 				const char *initprivs_expr,
-				const char *obj_kind, bool binary_upgrade)
+				const char *obj_kind, bool binary_upgrade, const char *case_stmt)
 {
 	/*
 	 * To get the delta from what the permissions were at creation time
@@ -762,32 +762,48 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 	printfPQExpBuffer(acl_subquery,
 					  "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
 					  "(SELECT acl, row_n FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,"
+					  "CASE WHEN %s THEN "
+					  "pg_catalog.acldefault(%s,%s) "
+					  "ELSE NULL END)) "
 					  "WITH ORDINALITY AS perm(acl,row_n) "
 					  "WHERE NOT EXISTS ( "
 					  "SELECT 1 FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,"
+					  "CASE WHEN %s THEN "
+					  "pg_catalog.acldefault(%s,%s) "
+					  "ELSE NULL END)) "
 					  "AS init(init_acl) WHERE acl = init_acl)) as foo)",
 					  acl_column,
+					  case_stmt ? case_stmt : "TRUE",
 					  obj_kind,
 					  acl_owner,
 					  initprivs_expr,
+					  case_stmt ? case_stmt : "TRUE",
 					  obj_kind,
 					  acl_owner);
 
 	printfPQExpBuffer(racl_subquery,
 					  "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
 					  "(SELECT acl, row_n FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,"
+					  "CASE WHEN %s THEN "
+					  "pg_catalog.acldefault(%s,%s) "
+					  "ELSE NULL END)) "
 					  "WITH ORDINALITY AS initp(acl,row_n) "
 					  "WHERE NOT EXISTS ( "
 					  "SELECT 1 FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,"
+					  "CASE WHEN %s THEN "
+					  "pg_catalog.acldefault(%s,%s) "
+					  "ELSE NULL END)) "
 					  "AS permp(orig_acl) WHERE acl = orig_acl)) as foo)",
 					  initprivs_expr,
+					  case_stmt ? case_stmt : "TRUE",
 					  obj_kind,
 					  acl_owner,
 					  acl_column,
+					  case_stmt ? case_stmt : "TRUE",
 					  obj_kind,
 					  acl_owner);
 
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index f5465f19ae..b5b1792fb3 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -55,7 +55,7 @@ extern void buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 							PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery,
 							const char *acl_column, const char *acl_owner,
 							const char *initprivs_expr,
-							const char *obj_kind, bool binary_upgrade);
+							const char *obj_kind, bool binary_upgrade, const char *case_stmt);
 
 extern bool variable_is_guc_list_quote(const char *name);
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..301868e4e6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3421,7 +3421,7 @@ getBlobs(Archive *fout)
 
 		buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery,
 						init_racl_subquery, "l.lomacl", "l.lomowner",
-						"pip.initprivs", "'L'", dopt->binary_upgrade);
+						"pip.initprivs", "'L'", dopt->binary_upgrade, NULL);
 
 		appendPQExpBuffer(blobQry,
 						  "SELECT l.oid, (%s l.lomowner) AS rolname, "
@@ -4874,7 +4874,7 @@ getNamespaces(Archive *fout, int *numNamespaces)
 						"         n.nspowner::regrole, n.nspowner::regrole),"
 						"  format('=UC/%s', n.nspowner::regrole)]::aclitem[] "
 						"ELSE pip.initprivs END",
-						"'n'", dopt->binary_upgrade);
+						"'n'", dopt->binary_upgrade, NULL);
 
 		appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, "
 						  "n.nspowner, "
@@ -5125,7 +5125,7 @@ getTypes(Archive *fout, int *numTypes)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "t.typacl", "t.typowner",
-						"pip.initprivs", "'T'", dopt->binary_upgrade);
+						"pip.initprivs", "'T'", dopt->binary_upgrade, NULL);
 
 		appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, "
 						  "t.typnamespace, "
@@ -5827,7 +5827,7 @@ getAggregates(Archive *fout, int *numAggs)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "p.proacl", "p.proowner",
-						"pip.initprivs", "'f'", dopt->binary_upgrade);
+						"pip.initprivs", "'f'", dopt->binary_upgrade, NULL);
 
 		agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'"
 					 : "p.proisagg");
@@ -6040,7 +6040,7 @@ getFuncs(Archive *fout, int *numFuncs)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "p.proacl", "p.proowner",
-						"pip.initprivs", "'f'", dopt->binary_upgrade);
+						"pip.initprivs", "'f'", dopt->binary_upgrade, NULL);
 
 		not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'"
 						 : "NOT p.proisagg");
@@ -6339,11 +6339,11 @@ getTables(Archive *fout, int *numTables)
 						"pip.initprivs",
 						"CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
 						" THEN 's' ELSE 'r' END::\"char\"",
-						dopt->binary_upgrade);
+						dopt->binary_upgrade, NULL);
 
 		buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
 						attinitracl_subquery, "at.attacl", "c.relowner",
-						"pip.initprivs", "'c'", dopt->binary_upgrade);
+						"pip.initprivs", "'c'", dopt->binary_upgrade, NULL);
 
 		appendPQExpBuffer(query,
 						  "SELECT c.tableoid, c.oid, c.relname, "
@@ -8318,7 +8318,7 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "l.lanacl", "l.lanowner",
-						"pip.initprivs", "'l'", dopt->binary_upgrade);
+						"pip.initprivs", "'l'", dopt->binary_upgrade, NULL);
 
 		/* pg_language has a laninline column */
 		appendPQExpBuffer(query, "SELECT l.tableoid, l.oid, "
@@ -9509,7 +9509,7 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "f.fdwacl", "f.fdwowner",
-						"pip.initprivs", "'F'", dopt->binary_upgrade);
+						"pip.initprivs", "'F'", dopt->binary_upgrade, NULL);
 
 		appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.fdwname, "
 						  "(%s f.fdwowner) AS rolname, "
@@ -9676,7 +9676,7 @@ getForeignServers(Archive *fout, int *numForeignServers)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "f.srvacl", "f.srvowner",
-						"pip.initprivs", "'S'", dopt->binary_upgrade);
+						"pip.initprivs", "'S'", dopt->binary_upgrade, NULL);
 
 		appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.srvname, "
 						  "(%s f.srvowner) AS rolname, "
@@ -9824,7 +9824,7 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs)
 						initracl_subquery, "defaclacl", "defaclrole",
 						"pip.initprivs",
 						"CASE WHEN defaclobjtype = 'S' THEN 's' ELSE defaclobjtype END::\"char\"",
-						dopt->binary_upgrade);
+						dopt->binary_upgrade, "defaclnamespace = 0");
 
 		appendPQExpBuffer(query, "SELECT d.oid, d.tableoid, "
 						  "(%s d.defaclrole) AS defaclrole, "
@@ -15689,7 +15689,7 @@ dumpTable(Archive *fout, const TableInfo *tbinfo)
 
 			buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 							initracl_subquery, "at.attacl", "c.relowner",
-							"pip.initprivs", "'c'", dopt->binary_upgrade);
+							"pip.initprivs", "'c'", dopt->binary_upgrade, NULL);
 
 			appendPQExpBuffer(query,
 							  "SELECT at.attname, "
-- 
2.16.6

#2Muhammad Usama
m.usama@gmail.com
In reply to: Bossart, Nathan (#1)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: not tested

The patch looks fine and successfully fixes the issue of missing privileges issue.

The new status of this patch is: Ready for Committer

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bossart, Nathan (#1)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

"Bossart, Nathan" <bossartn@amazon.com> writes:

The problem appears to be that pg_dump is comparing the entries in
pg_default_acl to the default ACL (i.e., acldefault()). This is fine
for "global" entries (i.e., entries with no schema specified), but it
doesn't work for "non-global" entries (i.e., entries with a schema
specified). This is because the default for a non-global entry is
actually an empty ACL.

Good point.

I've attached a quick hack that seems to fix this by adjusting the
pg_dump query to use NULL instead of acldefault() for non-global
entries. I'm posting this early in order to gather thoughts on the
approach and to make sure I'm not missing something obvious.

I find this impossible to comment on as to correctness, because the patch
is nigh unreadable. "case_stmt" is a pretty darn opaque variable name,
and the lack of comments doesn't help, and I don't really think that you
chose good semantics for it anyway. Probably it would be better for the
new argument to be along the lines of "bool is_default_acl", and allow
buildACLQueries to know what it should put in when that's true.

I'm kind of allergic to this SQL coding style, too. It expects the
backend to expend many thousands of cycles parsing and then optimizing
away a useless CASE, to save a couple of lines of code and a few cycles
on the client side. Nor is doing the query this way even particularly
readable on the client side.

Lastly, there probably should be a test case or two.

regards, tom lane

#4Bossart, Nathan
bossartn@amazon.com
In reply to: Tom Lane (#3)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

On 9/5/21, 10:08 AM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

"Bossart, Nathan" <bossartn@amazon.com> writes:

I've attached a quick hack that seems to fix this by adjusting the
pg_dump query to use NULL instead of acldefault() for non-global
entries. I'm posting this early in order to gather thoughts on the
approach and to make sure I'm not missing something obvious.

I find this impossible to comment on as to correctness, because the patch
is nigh unreadable. "case_stmt" is a pretty darn opaque variable name,
and the lack of comments doesn't help, and I don't really think that you
chose good semantics for it anyway. Probably it would be better for the
new argument to be along the lines of "bool is_default_acl", and allow
buildACLQueries to know what it should put in when that's true.

My apologies. This definitely shouldn't have been marked as ready-
for-committer. FWIW this is exactly the sort of feedback I was hoping
to get before I expended more effort on this patch.

I'm kind of allergic to this SQL coding style, too. It expects the
backend to expend many thousands of cycles parsing and then optimizing
away a useless CASE, to save a couple of lines of code and a few cycles
on the client side. Nor is doing the query this way even particularly
readable on the client side.

Is there a specific style you have in mind for this change, or is your
point that the CASE statement should only be reserved for when
is_default_acl is true?

Lastly, there probably should be a test case or two.

Of course. That will be in the next revision.

Nathan

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bossart, Nathan (#4)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

"Bossart, Nathan" <bossartn@amazon.com> writes:

On 9/5/21, 10:08 AM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

I'm kind of allergic to this SQL coding style, too. It expects the
backend to expend many thousands of cycles parsing and then optimizing
away a useless CASE, to save a couple of lines of code and a few cycles
on the client side. Nor is doing the query this way even particularly
readable on the client side.

Is there a specific style you have in mind for this change, or is your
point that the CASE statement should only be reserved for when
is_default_acl is true?

I'd be inclined to split this logic out into a separate if-statement,
along the lines of

... build some of the query ...

if (is_default_acl)
{
/* The reference ACL is empty for schema-local default ACLs */
appendPQExpBuffer(..., "CASE WHEN ... THEN pg_catalog.acldefault(%s,%s) ELSE NULL END", ...);
}
else
{
/* For other cases, the reference is always acldefault() */
appendPQExpBuffer(..., "pg_catalog.acldefault(%s,%s)", ...);
}

... build rest of the query ...

I think this is more readable than one giant printf, and not incidentally
it allows room for some helpful comments.

(I kind of wonder if we shouldn't try to refactor buildACLQueries to
reduce code duplication and add comments while we're at it. The existing
code seems pretty awful from a readability standpoint already. I don't
have any clear idea about what to do instead, though.)

regards, tom lane

#6Bossart, Nathan
bossartn@amazon.com
In reply to: Tom Lane (#5)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

On 9/5/21, 12:14 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

"Bossart, Nathan" <bossartn@amazon.com> writes:

Is there a specific style you have in mind for this change, or is your
point that the CASE statement should only be reserved for when
is_default_acl is true?

I'd be inclined to split this logic out into a separate if-statement,
along the lines of

Got it, thanks.

Nathan

#7Bossart, Nathan
bossartn@amazon.com
In reply to: Tom Lane (#5)
1 attachment(s)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

On 9/5/21, 12:58 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:

On 9/5/21, 12:14 PM, "Tom Lane" <tgl@sss.pgh.pa.us> wrote:

I'd be inclined to split this logic out into a separate if-statement,
along the lines of

Got it, thanks.

Attached is an attempt at cleaning the patch up and adding an
informative comment and a test case.

Nathan

Attachments:

v2-0001-Fix-pg_dump-handling-of-ALTER-DEFAULT-PRIVILEGES-.patchapplication/octet-stream; name=v2-0001-Fix-pg_dump-handling-of-ALTER-DEFAULT-PRIVILEGES-.patchDownload
From cdffe044438135279515277797e5835997799317 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Mon, 6 Sep 2021 05:32:14 +0000
Subject: [PATCH v2 1/1] Fix pg_dump handling of ALTER DEFAULT PRIVILEGES IN
 SCHEMA.

When dumping ACLs, pg_dump uses the hard-wired default privileges
(i.e., acldefault()) as a reference.  While this is usually okay,
it sometimes doesn't work for schema-local default ACLs (i.e.,
pg_default_acl entries with defaclnamespace != 0).  This is because
the default for a schema-local default ACL entry is actually an
empty ACL, as per the following note in aclchk.c:

	/*
	 * The default for a global entry is the hard-wired default ACL for the
	 * particular object type.  The default for non-global entries is an empty
	 * ACL.  This must be so because global entries replace the hard-wired
	 * defaults, while others are added on.
	 */

To fix, adjust the pg_dump query for dumping ACLs to use NULL
instead of acldefault() for schema-local default ACLs.
---
 src/bin/pg_dump/dumputils.c      | 56 ++++++++++++++++++++++++++--------------
 src/bin/pg_dump/dumputils.h      |  3 ++-
 src/bin/pg_dump/pg_dump.c        | 25 +++++++++---------
 src/bin/pg_dump/t/002_pg_dump.pl | 19 ++++++++++++++
 4 files changed, 71 insertions(+), 32 deletions(-)

diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index ea67e52a3f..a6799bb5a5 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -726,8 +726,30 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 				PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery,
 				const char *acl_column, const char *acl_owner,
 				const char *initprivs_expr,
-				const char *obj_kind, bool binary_upgrade)
+				const char *obj_kind, bool binary_upgrade, bool is_default_acl)
 {
+	PQExpBuffer	acldefault_expr = createPQExpBuffer();
+
+	/*
+	 * Build the expression for obtaining the default privileges.
+	 *
+	 * This is ordinarily just acldefault(), but for schema-local default ACLs
+	 * (i.e., entries in pg_default_acl with defaclnamespace != 0), the default
+	 * is actually an empty ACL.
+	 */
+	if (is_default_acl)
+		printfPQExpBuffer(acldefault_expr,
+						  "CASE WHEN defaclnamespace = 0 THEN "
+						  "pg_catalog.acldefault(%s,%s) "
+						  "ELSE NULL END",
+						  obj_kind,
+						  acl_owner);
+	else
+		printfPQExpBuffer(acldefault_expr,
+						  "pg_catalog.acldefault(%s,%s)",
+						  obj_kind,
+						  acl_owner);
+
 	/*
 	 * To get the delta from what the permissions were at creation time
 	 * (either initdb or CREATE EXTENSION) vs. what they are now, we have to
@@ -762,34 +784,30 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 	printfPQExpBuffer(acl_subquery,
 					  "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
 					  "(SELECT acl, row_n FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,%s)) "
 					  "WITH ORDINALITY AS perm(acl,row_n) "
 					  "WHERE NOT EXISTS ( "
 					  "SELECT 1 FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,%s)) "
 					  "AS init(init_acl) WHERE acl = init_acl)) as foo)",
 					  acl_column,
-					  obj_kind,
-					  acl_owner,
+					  acldefault_expr->data,
 					  initprivs_expr,
-					  obj_kind,
-					  acl_owner);
+					  acldefault_expr->data);
 
 	printfPQExpBuffer(racl_subquery,
 					  "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM "
 					  "(SELECT acl, row_n FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,%s)) "
 					  "WITH ORDINALITY AS initp(acl,row_n) "
 					  "WHERE NOT EXISTS ( "
 					  "SELECT 1 FROM "
-					  "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) "
+					  "pg_catalog.unnest(coalesce(%s,%s)) "
 					  "AS permp(orig_acl) WHERE acl = orig_acl)) as foo)",
 					  initprivs_expr,
-					  obj_kind,
-					  acl_owner,
+					  acldefault_expr->data,
 					  acl_column,
-					  obj_kind,
-					  acl_owner);
+					  acldefault_expr->data);
 
 	/*
 	 * In binary upgrade mode we don't run the extension script but instead
@@ -814,23 +832,21 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 						  "WITH ORDINALITY AS initp(acl,row_n) "
 						  "WHERE NOT EXISTS ( "
 						  "SELECT 1 FROM "
-						  "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) "
+						  "pg_catalog.unnest(%s) "
 						  "AS privm(orig_acl) WHERE acl = orig_acl)) as foo) END",
 						  initprivs_expr,
-						  obj_kind,
-						  acl_owner);
+						  acldefault_expr->data);
 
 		printfPQExpBuffer(init_racl_subquery,
 						  "CASE WHEN privtype = 'e' THEN "
 						  "(SELECT pg_catalog.array_agg(acl) FROM "
 						  "(SELECT acl, row_n FROM "
-						  "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) "
+						  "pg_catalog.unnest(%s) "
 						  "WITH ORDINALITY AS privp(acl,row_n) "
 						  "WHERE NOT EXISTS ( "
 						  "SELECT 1 FROM pg_catalog.unnest(%s) "
 						  "AS initp(init_acl) WHERE acl = init_acl)) as foo) END",
-						  obj_kind,
-						  acl_owner,
+						  acldefault_expr->data,
 						  initprivs_expr);
 	}
 	else
@@ -838,6 +854,8 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 		printfPQExpBuffer(init_acl_subquery, "NULL");
 		printfPQExpBuffer(init_racl_subquery, "NULL");
 	}
+
+	destroyPQExpBuffer(acldefault_expr);
 }
 
 /*
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index f5465f19ae..037f4b03bb 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -55,7 +55,8 @@ extern void buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
 							PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery,
 							const char *acl_column, const char *acl_owner,
 							const char *initprivs_expr,
-							const char *obj_kind, bool binary_upgrade);
+							const char *obj_kind, bool binary_upgrade,
+							bool is_default_acl);
 
 extern bool variable_is_guc_list_quote(const char *name);
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 67be849829..840d5a7a7a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3421,7 +3421,7 @@ getBlobs(Archive *fout)
 
 		buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery,
 						init_racl_subquery, "l.lomacl", "l.lomowner",
-						"pip.initprivs", "'L'", dopt->binary_upgrade);
+						"pip.initprivs", "'L'", dopt->binary_upgrade, false);
 
 		appendPQExpBuffer(blobQry,
 						  "SELECT l.oid, (%s l.lomowner) AS rolname, "
@@ -4866,7 +4866,7 @@ getNamespaces(Archive *fout, int *numNamespaces)
 						"         n.nspowner::regrole, n.nspowner::regrole),"
 						"  format('=UC/%s', n.nspowner::regrole)]::aclitem[] "
 						"ELSE pip.initprivs END",
-						"'n'", dopt->binary_upgrade);
+						"'n'", dopt->binary_upgrade, false);
 
 		appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, "
 						  "n.nspowner, "
@@ -5117,7 +5117,7 @@ getTypes(Archive *fout, int *numTypes)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "t.typacl", "t.typowner",
-						"pip.initprivs", "'T'", dopt->binary_upgrade);
+						"pip.initprivs", "'T'", dopt->binary_upgrade, false);
 
 		appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, "
 						  "t.typnamespace, "
@@ -5820,7 +5820,7 @@ getAggregates(Archive *fout, int *numAggs)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "p.proacl", "p.proowner",
-						"pip.initprivs", "'f'", dopt->binary_upgrade);
+						"pip.initprivs", "'f'", dopt->binary_upgrade, false);
 
 		agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'"
 					 : "p.proisagg");
@@ -6033,7 +6033,7 @@ getFuncs(Archive *fout, int *numFuncs)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "p.proacl", "p.proowner",
-						"pip.initprivs", "'f'", dopt->binary_upgrade);
+						"pip.initprivs", "'f'", dopt->binary_upgrade, false);
 
 		not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'"
 						 : "NOT p.proisagg");
@@ -6332,11 +6332,11 @@ getTables(Archive *fout, int *numTables)
 						"pip.initprivs",
 						"CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
 						" THEN 's' ELSE 'r' END::\"char\"",
-						dopt->binary_upgrade);
+						dopt->binary_upgrade, false);
 
 		buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
 						attinitracl_subquery, "at.attacl", "c.relowner",
-						"pip.initprivs", "'c'", dopt->binary_upgrade);
+						"pip.initprivs", "'c'", dopt->binary_upgrade, false);
 
 		appendPQExpBuffer(query,
 						  "SELECT c.tableoid, c.oid, c.relname, "
@@ -8311,7 +8311,7 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "l.lanacl", "l.lanowner",
-						"pip.initprivs", "'l'", dopt->binary_upgrade);
+						"pip.initprivs", "'l'", dopt->binary_upgrade, false);
 
 		/* pg_language has a laninline column */
 		appendPQExpBuffer(query, "SELECT l.tableoid, l.oid, "
@@ -9543,7 +9543,7 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "f.fdwacl", "f.fdwowner",
-						"pip.initprivs", "'F'", dopt->binary_upgrade);
+						"pip.initprivs", "'F'", dopt->binary_upgrade, false);
 
 		appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.fdwname, "
 						  "(%s f.fdwowner) AS rolname, "
@@ -9710,7 +9710,7 @@ getForeignServers(Archive *fout, int *numForeignServers)
 
 		buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 						initracl_subquery, "f.srvacl", "f.srvowner",
-						"pip.initprivs", "'S'", dopt->binary_upgrade);
+						"pip.initprivs", "'S'", dopt->binary_upgrade, false);
 
 		appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.srvname, "
 						  "(%s f.srvowner) AS rolname, "
@@ -9858,7 +9858,7 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs)
 						initracl_subquery, "defaclacl", "defaclrole",
 						"pip.initprivs",
 						"CASE WHEN defaclobjtype = 'S' THEN 's' ELSE defaclobjtype END::\"char\"",
-						dopt->binary_upgrade);
+						dopt->binary_upgrade, true);
 
 		appendPQExpBuffer(query, "SELECT d.oid, d.tableoid, "
 						  "(%s d.defaclrole) AS defaclrole, "
@@ -15730,7 +15730,8 @@ dumpTable(Archive *fout, const TableInfo *tbinfo)
 
 			buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
 							initracl_subquery, "at.attacl", "c.relowner",
-							"pip.initprivs", "'c'", dopt->binary_upgrade);
+							"pip.initprivs", "'c'", dopt->binary_upgrade,
+							false);
 
 			appendPQExpBuffer(query,
 							  "SELECT at.attname, "
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e1b7e31458..6662d0b92b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -475,6 +475,25 @@ my %tests = (
 		unlike => { no_privs => 1, },
 	  },
 
+	'ALTER DEFAULT PRIVILEGES FOR ROLE ... IN SCHEMA ...'
+	  => {
+		create_order => 57,
+		create_sql   => 'ALTER DEFAULT PRIVILEGES
+					   FOR ROLE regress_dump_test_role IN SCHEMA dump_test
+					   GRANT ALL ON FUNCTIONS TO PUBLIC;',
+		regexp => qr/^
+			\QALTER DEFAULT PRIVILEGES \E
+			\QFOR ROLE regress_dump_test_role IN SCHEMA dump_test \E
+			\QGRANT ALL ON FUNCTIONS  TO PUBLIC;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
+		unlike => {
+			exclude_dump_test_schema => 1,
+			no_privs                 => 1,
+		},
+	  },
+
 	'ALTER ROLE regress_dump_test_role' => {
 		regexp => qr/^
 			\QALTER ROLE regress_dump_test_role WITH \E
-- 
2.16.6

#8Bossart, Nathan
bossartn@amazon.com
In reply to: Tom Lane (#5)
Re: pg_dump handling of ALTER DEFAULT PRIVILEGES IN SCHEMA

On 9/5/21, 11:33 PM, "Bossart, Nathan" <bossartn@amazon.com> wrote:

Attached is an attempt at cleaning the patch up and adding an
informative comment and a test case.

For future reference, there was another thread for this bug [0]/messages/by-id/CAA3qoJnr2+1dVJObNtfec=qW4Z0nz=A9+r5bZKoTSy5RDjskMw@mail.gmail.com, and a
fix was committed [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2acc84c.

Nathan

[0]: /messages/by-id/CAA3qoJnr2+1dVJObNtfec=qW4Z0nz=A9+r5bZKoTSy5RDjskMw@mail.gmail.com
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2acc84c