From cdffe044438135279515277797e5835997799317 Mon Sep 17 00:00:00 2001 From: Nathan Bossart 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