From 0c9d84427f441602425b0e18be5cfe751d1d8ebe Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 6 Dec 2021 12:39:45 -0500 Subject: [PATCH] Rethink pg_dump's handling of object ACLs. Throw away most of the existing logic for this, as it was very inefficient thanks to expensive sub-selects executed to collect ACL data that we very possibly would have no interest in dumping. Reduce the ACL handling in the initial per-object-type queries to be just collection of the catalog ACL fields, as it was originally. Fetch pg_init_privs data separately in a single scan of that catalog, and do the merging calculations on the client side. Remove the separate code path used for pre-9.6 source servers; there is no good reason to treat them differently from newer servers that happen to have empty pg_init_privs. Discussion: https://postgr.es/m/2273648.1634764485@sss.pgh.pa.us Discussion: https://postgr.es/m/7d7eb6128f40401d81b3b7a898b6b4de@W2012-02.nidsa.loc --- src/bin/pg_dump/dumputils.c | 516 +++++------- src/bin/pg_dump/dumputils.h | 14 +- src/bin/pg_dump/pg_dump.c | 1199 +++++++++++---------------- src/bin/pg_dump/pg_dump.h | 69 +- src/bin/pg_dump/pg_dumpall.c | 59 +- src/fe_utils/string_utils.c | 63 ++ src/include/fe_utils/string_utils.h | 1 + 7 files changed, 784 insertions(+), 1137 deletions(-) diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index ea67e52a3f4..6e216313c6d 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -24,7 +24,7 @@ static bool parseAclItem(const char *item, const char *type, const char *name, const char *subname, int remoteVersion, PQExpBuffer grantee, PQExpBuffer grantor, PQExpBuffer privs, PQExpBuffer privswgo); -static char *copyAclUserName(PQExpBuffer output, char *input); +static char *dequoteAclUserName(PQExpBuffer output, char *input); static void AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname); @@ -39,7 +39,8 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE, * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT) * acls: the ACL string fetched from the database - * racls: the ACL string of any initial-but-now-revoked privileges + * baseacls: the initial ACL string for this object; can be + * NULL or empty string to indicate "not available from server" * owner: username of object owner (will be passed through fmtId); can be * NULL or empty string to indicate "no owner known" * prefix: string to prefix to each generated command; typically empty @@ -48,6 +49,12 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, * Returns true if okay, false if could not parse the acl string. * The resulting commands (if any) are appended to the contents of 'sql'. * + * baseacls is typically the result of acldefault() for the object's type + * and owner. However, if there is a pg_init_privs entry for the object, + * it should instead be the initprivs ACLs. When acls is itself a + * pg_init_privs entry, baseacls is what to dump that relative to; then + * it can be either an acldefault() value or an empty ACL "{}". + * * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES " * or something similar, and name is an empty string. * @@ -56,15 +63,19 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, */ bool buildACLCommands(const char *name, const char *subname, const char *nspname, - const char *type, const char *acls, const char *racls, + const char *type, const char *acls, const char *baseacls, const char *owner, const char *prefix, int remoteVersion, PQExpBuffer sql) { bool ok = true; char **aclitems = NULL; - char **raclitems = NULL; + char **baseitems = NULL; + char **grantitems = NULL; + char **revokeitems = NULL; int naclitems = 0; - int nraclitems = 0; + int nbaseitems = 0; + int ngrantitems = 0; + int nrevokeitems = 0; int i; PQExpBuffer grantee, grantor, @@ -72,37 +83,88 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, privswgo; PQExpBuffer firstsql, secondsql; - bool found_owner_privs = false; - if (strlen(acls) == 0 && strlen(racls) == 0) + /* + * If the acl was NULL (initial default state), we need do nothing. Note + * that this is distinguishable from all-privileges-revoked, which will + * look like an empty array ("{}"). + */ + if (acls == NULL || *acls == '\0') return true; /* object has default permissions */ /* treat empty-string owner same as NULL */ if (owner && *owner == '\0') owner = NULL; - if (strlen(acls) != 0) + /* Parse the acls array */ + if (!parsePGArray(acls, &aclitems, &naclitems)) + { + if (aclitems) + free(aclitems); + return false; + } + + /* Parse the baseacls, if provided */ + if (baseacls && *baseacls != '\0') { - if (!parsePGArray(acls, &aclitems, &naclitems)) + if (!parsePGArray(baseacls, &baseitems, &nbaseitems)) { if (aclitems) free(aclitems); + if (baseitems) + free(baseitems); return false; } } - if (strlen(racls) != 0) + /* + * Compare the actual ACL with the base ACL, extracting the privileges + * that need to be granted (i.e., are in the actual ACL but not the base + * ACL) and the ones that need to be revoked (the reverse). We use plain + * string comparisons to check for matches. In principle that could be + * fooled by extraneous issues such as whitespace, but since all these + * strings are the work of aclitemout(), it should be OK in practice. + * Besides, a false mismatch will just cause the output to be a little + * more verbose than it really needed to be. + * + * (If we weren't given a base ACL, this stanza winds up with all the + * ACL's items in grantitems and nothing in revokeitems. It's not worth + * special-casing that.) + */ + grantitems = (char **) pg_malloc(naclitems * sizeof(char *)); + for (i = 0; i < naclitems; i++) { - if (!parsePGArray(racls, &raclitems, &nraclitems)) + bool found = false; + + for (int j = 0; j < nbaseitems; j++) { - if (aclitems) - free(aclitems); - if (raclitems) - free(raclitems); - return false; + if (strcmp(aclitems[i], baseitems[j]) == 0) + { + found = true; + break; + } } + if (!found) + grantitems[ngrantitems++] = aclitems[i]; } + revokeitems = (char **) pg_malloc(nbaseitems * sizeof(char *)); + for (i = 0; i < nbaseitems; i++) + { + bool found = false; + for (int j = 0; j < naclitems; j++) + { + if (strcmp(baseitems[i], aclitems[j]) == 0) + { + found = true; + break; + } + } + if (!found) + revokeitems[nrevokeitems++] = baseitems[i]; + } + + /* Prepare working buffers */ grantee = createPQExpBuffer(); grantor = createPQExpBuffer(); privs = createPQExpBuffer(); @@ -110,50 +172,21 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, /* * At the end, these two will be pasted together to form the result. - * - * For older systems we use these to ensure that the owner privileges go - * before the other ones, as a GRANT could create the default entry for - * the object, which generally includes all rights for the owner. In more - * recent versions we normally handle this because the owner rights come - * first in the ACLs, but older versions might have them after the PUBLIC - * privileges. - * - * For 9.6 and later systems, much of this changes. With 9.6, we check - * the default privileges for the objects at dump time and create two sets - * of ACLs- "racls" which are the ACLs to REVOKE from the object (as the - * object may have initial privileges on it, along with any default ACLs - * which are not part of the current set of privileges), and regular - * "acls", which are the ACLs to GRANT to the object. We handle the - * REVOKEs first, followed by the GRANTs. */ firstsql = createPQExpBuffer(); secondsql = createPQExpBuffer(); /* - * For pre-9.6 systems, we always start with REVOKE ALL FROM PUBLIC, as we - * don't wish to make any assumptions about what the default ACLs are, and - * we do not collect them during the dump phase (and racls will always be - * the empty set, see above). - * - * For 9.6 and later, if any revoke ACLs have been provided, then include - * them in 'firstsql'. + * If we weren't given baseacls information, we just revoke everything and + * then grant what's listed in the ACL. This avoids having to embed + * detailed knowledge about what the defaults are/were, and it's not very + * expensive since servers lacking acldefault() are now rare. * - * Revoke ACLs happen when an object starts out life with a set of - * privileges (eg: GRANT SELECT ON pg_class TO PUBLIC;) and the user has - * decided to revoke those rights. Since those objects come into being - * with those default privileges, we have to revoke them to match what the - * current state of affairs is. Note that we only started explicitly - * tracking such initial rights in 9.6, and prior to that all initial - * rights are actually handled by the simple 'REVOKE ALL .. FROM PUBLIC' - * case, for initdb-created objects. Prior to 9.6, we didn't handle - * extensions correctly, but we do now by tracking their initial - * privileges, in the same way we track initdb initial privileges, see - * pg_init_privs. + * Otherwise, we need only revoke what's listed in revokeitems. */ - if (remoteVersion < 90600) + if (baseacls == NULL || *baseacls == '\0') { - Assert(nraclitems == 0); - + /* We assume the old defaults only involved the owner and PUBLIC */ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); @@ -161,13 +194,24 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, if (nspname && *nspname) appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); appendPQExpBuffer(firstsql, "%s FROM PUBLIC;\n", name); + if (owner) + { + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); + if (subname) + appendPQExpBuffer(firstsql, "(%s)", subname); + appendPQExpBuffer(firstsql, " ON %s ", type); + if (nspname && *nspname) + appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); + appendPQExpBuffer(firstsql, "%s FROM %s;\n", name, fmtId(owner)); + } } else { /* Scan individual REVOKE ACL items */ - for (i = 0; i < nraclitems; i++) + for (i = 0; i < nrevokeitems; i++) { - if (!parseAclItem(raclitems[i], type, name, subname, remoteVersion, + if (!parseAclItem(revokeitems[i], + type, name, subname, remoteVersion, grantee, grantor, privs, NULL)) { ok = false; @@ -195,6 +239,10 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, } /* + * At this point we have issued REVOKE statements for all initial and + * default privileges that are no longer present on the object, so we are + * almost ready to GRANT the privileges listed in grantitems[]. + * * We still need some hacking though to cover the case where new default * public privileges are added in new versions: the REVOKE ALL will revoke * them, leading to behavior different from what the old version had, @@ -208,146 +256,92 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, prefix, type, name); } - /* Scan individual ACL items */ - for (i = 0; i < naclitems; i++) + /* + * Scan individual ACL items to be granted. + * + * The order in which privileges appear in the ACL string (the order they + * have been GRANT'd in, which the backend maintains) must be preserved to + * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on + * those are dumped in the correct order. However, some old server + * versions will show grants to PUBLIC before the owner's own grants; for + * consistency's sake, force the owner's grants to be output first. + */ + for (i = 0; i < ngrantitems; i++) { - if (!parseAclItem(aclitems[i], type, name, subname, remoteVersion, - grantee, grantor, privs, privswgo)) - { - ok = false; - break; - } - - if (grantor->len == 0 && owner) - printfPQExpBuffer(grantor, "%s", owner); - - if (privs->len > 0 || privswgo->len > 0) + if (parseAclItem(grantitems[i], type, name, subname, remoteVersion, + grantee, grantor, privs, privswgo)) { /* - * Prior to 9.6, we had to handle owner privileges in a special - * manner by first REVOKE'ing the rights and then GRANT'ing them - * after. With 9.6 and above, what we need to REVOKE and what we - * need to GRANT is figured out when we dump and stashed into - * "racls" and "acls", respectively. See above. + * If the grantor isn't the owner, we'll need to use SET SESSION + * AUTHORIZATION to become the grantor. Issue the SET/RESET only + * if there's something useful to do. */ - if (remoteVersion < 90600 && owner - && strcmp(grantee->data, owner) == 0 - && strcmp(grantor->data, owner) == 0) + if (privs->len > 0 || privswgo->len > 0) { - found_owner_privs = true; + PQExpBuffer thissql; + + /* Set owner as grantor if that's not explicit in the ACL */ + if (grantor->len == 0 && owner) + printfPQExpBuffer(grantor, "%s", owner); + + /* Make sure owner's own grants are output before others */ + if (owner && + strcmp(grantee->data, owner) == 0 && + strcmp(grantor->data, owner) == 0) + thissql = firstsql; + else + thissql = secondsql; - /* - * For the owner, the default privilege level is ALL WITH - * GRANT OPTION. - */ - if (strcmp(privswgo->data, "ALL") != 0) - { - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s ", type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, "%s FROM %s;\n", - name, fmtId(grantee->data)); - if (privs->len > 0) - { - appendPQExpBuffer(firstsql, - "%sGRANT %s ON %s ", - prefix, privs->data, type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, - "%s TO %s;\n", - name, fmtId(grantee->data)); - } - if (privswgo->len > 0) - { - appendPQExpBuffer(firstsql, - "%sGRANT %s ON %s ", - prefix, privswgo->data, type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, - "%s TO %s WITH GRANT OPTION;\n", - name, fmtId(grantee->data)); - } - } - } - else - { - /* - * For systems prior to 9.6, we can assume we are starting - * from no privs at this point. - * - * For 9.6 and above, at this point we have issued REVOKE - * statements for all initial and default privileges which are - * no longer present on the object (as they were passed in as - * 'racls') and we can simply GRANT the rights which are in - * 'acls'. - */ if (grantor->len > 0 && (!owner || strcmp(owner, grantor->data) != 0)) - appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n", + appendPQExpBuffer(thissql, "SET SESSION AUTHORIZATION %s;\n", fmtId(grantor->data)); if (privs->len > 0) { - appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ", + appendPQExpBuffer(thissql, "%sGRANT %s ON %s ", prefix, privs->data, type); if (nspname && *nspname) - appendPQExpBuffer(secondsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(secondsql, "%s TO ", name); + appendPQExpBuffer(thissql, "%s.", fmtId(nspname)); + appendPQExpBuffer(thissql, "%s TO ", name); if (grantee->len == 0) - appendPQExpBufferStr(secondsql, "PUBLIC;\n"); + appendPQExpBufferStr(thissql, "PUBLIC;\n"); else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) - appendPQExpBuffer(secondsql, "GROUP %s;\n", + appendPQExpBuffer(thissql, "GROUP %s;\n", fmtId(grantee->data + strlen("group "))); else - appendPQExpBuffer(secondsql, "%s;\n", fmtId(grantee->data)); + appendPQExpBuffer(thissql, "%s;\n", fmtId(grantee->data)); } if (privswgo->len > 0) { - appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ", + appendPQExpBuffer(thissql, "%sGRANT %s ON %s ", prefix, privswgo->data, type); if (nspname && *nspname) - appendPQExpBuffer(secondsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(secondsql, "%s TO ", name); + appendPQExpBuffer(thissql, "%s.", fmtId(nspname)); + appendPQExpBuffer(thissql, "%s TO ", name); if (grantee->len == 0) - appendPQExpBufferStr(secondsql, "PUBLIC"); + appendPQExpBufferStr(thissql, "PUBLIC"); else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) - appendPQExpBuffer(secondsql, "GROUP %s", + appendPQExpBuffer(thissql, "GROUP %s", fmtId(grantee->data + strlen("group "))); else - appendPQExpBufferStr(secondsql, fmtId(grantee->data)); - appendPQExpBufferStr(secondsql, " WITH GRANT OPTION;\n"); + appendPQExpBufferStr(thissql, fmtId(grantee->data)); + appendPQExpBufferStr(thissql, " WITH GRANT OPTION;\n"); } if (grantor->len > 0 && (!owner || strcmp(owner, grantor->data) != 0)) - appendPQExpBufferStr(secondsql, "RESET SESSION AUTHORIZATION;\n"); + appendPQExpBufferStr(thissql, "RESET SESSION AUTHORIZATION;\n"); } } - } - - /* - * For systems prior to 9.6, if we didn't find any owner privs, the owner - * must have revoked 'em all. - * - * For 9.6 and above, we handle this through the 'racls'. See above. - */ - if (remoteVersion < 90600 && !found_owner_privs && owner) - { - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s ", type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, "%s FROM %s;\n", - name, fmtId(owner)); + else + { + /* parseAclItem failed, give up */ + ok = false; + break; + } } destroyPQExpBuffer(grantee); @@ -361,19 +355,23 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, if (aclitems) free(aclitems); - - if (raclitems) - free(raclitems); + if (baseitems) + free(baseitems); + if (grantitems) + free(grantitems); + if (revokeitems) + free(revokeitems); return ok; } /* - * Build ALTER DEFAULT PRIVILEGES command(s) for single pg_default_acl entry. + * Build ALTER DEFAULT PRIVILEGES command(s) for a single pg_default_acl entry. * * type: the object type (TABLES, FUNCTIONS, etc) * nspname: schema name, or NULL for global default privileges * acls: the ACL string fetched from the database + * acldefault: the appropriate default ACL for the object type and owner * owner: username of privileges owner (will be passed through fmtId) * remoteVersion: version of database * @@ -382,8 +380,7 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, */ bool buildDefaultACLCommands(const char *type, const char *nspname, - const char *acls, const char *racls, - const char *initacls, const char *initracls, + const char *acls, const char *acldefault, const char *owner, int remoteVersion, PQExpBuffer sql) @@ -403,21 +400,12 @@ buildDefaultACLCommands(const char *type, const char *nspname, if (nspname) appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname)); - if (strlen(initacls) != 0 || strlen(initracls) != 0) - { - appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\n"); - if (!buildACLCommands("", NULL, NULL, type, - initacls, initracls, owner, - prefix->data, remoteVersion, sql)) - { - destroyPQExpBuffer(prefix); - return false; - } - appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\n"); - } - + /* + * There's no such thing as initprivs for a default ACL, so the base ACL + * is always just the object-type-specific default. + */ if (!buildACLCommands("", NULL, NULL, type, - acls, racls, owner, + acls, acldefault, owner, prefix->data, remoteVersion, sql)) { destroyPQExpBuffer(prefix); @@ -467,7 +455,7 @@ parseAclItem(const char *item, const char *type, buf = pg_strdup(item); /* user or group name is string up to = */ - eqpos = copyAclUserName(grantee, buf); + eqpos = dequoteAclUserName(grantee, buf); if (*eqpos != '=') { pg_free(buf); @@ -479,7 +467,7 @@ parseAclItem(const char *item, const char *type, if (slpos) { *slpos++ = '\0'; - slpos = copyAclUserName(grantor, slpos); + slpos = dequoteAclUserName(grantor, slpos); if (*slpos != '\0') { pg_free(buf); @@ -603,13 +591,46 @@ do { \ return true; } +/* + * Transfer the role name at *input into the output buffer, adding + * quoting according to the same rules as putid() in backend's acl.c. + */ +void +quoteAclUserName(PQExpBuffer output, const char *input) +{ + const char *src; + bool safe = true; + + for (src = input; *src; src++) + { + /* This test had better match what putid() does */ + if (!isalnum((unsigned char) *src) && *src != '_') + { + safe = false; + break; + } + } + if (!safe) + appendPQExpBufferChar(output, '"'); + for (src = input; *src; src++) + { + /* A double quote character in a username is encoded as "" */ + if (*src == '"') + appendPQExpBufferChar(output, '"'); + appendPQExpBufferChar(output, *src); + } + if (!safe) + appendPQExpBufferChar(output, '"'); +} + /* * Transfer a user or group name starting at *input into the output buffer, * dequoting if needed. Returns a pointer to just past the input name. * The name is taken to end at an unquoted '=' or end of string. + * Note: unlike quoteAclUserName(), this first clears the output buffer. */ static char * -copyAclUserName(PQExpBuffer output, char *input) +dequoteAclUserName(PQExpBuffer output, char *input) { resetPQExpBuffer(output); @@ -708,137 +729,6 @@ emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer, } } -/* - * buildACLQueries - * - * Build the subqueries to extract out the correct set of ACLs to be - * GRANT'd and REVOKE'd for the specific kind of object, accounting for any - * initial privileges (from pg_init_privs) and based on if we are in binary - * upgrade mode or not. - * - * Also builds subqueries to extract out the set of ACLs to go from the object - * default privileges to the privileges in pg_init_privs, if we are in binary - * upgrade mode, so that those privileges can be set up and recorded in the new - * cluster before the regular privileges are added on top of those. - */ -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) -{ - /* - * 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 - * look at two things: - * - * What privileges have been added, which we calculate by extracting all - * the current privileges (using the set of default privileges for the - * object type if current privileges are NULL) and then removing those - * which existed at creation time (again, using the set of default - * privileges for the object type if there were no creation time - * privileges). - * - * What privileges have been removed, which we calculate by extracting the - * privileges as they were at creation time (or the default privileges, as - * above), and then removing the current privileges (or the default - * privileges, if current privileges are NULL). - * - * As a good cross-check, both directions of these checks should result in - * the empty set if both the current ACL and the initial privs are NULL - * (meaning, in practice, that the default ACLs were there at init time - * and is what the current privileges are). - * - * We always perform this delta on all ACLs and expect that by the time - * these are run the initial privileges will be in place, even in a binary - * upgrade situation (see below). - * - * Finally, the order in which privileges are in the ACL string (the order - * they been GRANT'd in, which the backend maintains) must be preserved to - * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on - * those are dumped in the correct order. - */ - 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))) " - "WITH ORDINALITY AS perm(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM " - "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " - "AS init(init_acl) WHERE acl = init_acl)) as foo)", - acl_column, - obj_kind, - acl_owner, - initprivs_expr, - 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))) " - "WITH ORDINALITY AS initp(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM " - "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " - "AS permp(orig_acl) WHERE acl = orig_acl)) as foo)", - initprivs_expr, - obj_kind, - acl_owner, - acl_column, - obj_kind, - acl_owner); - - /* - * In binary upgrade mode we don't run the extension script but instead - * dump out the objects independently and then recreate them. To preserve - * the initial privileges which were set on extension objects, we need to - * grab the set of GRANT and REVOKE commands necessary to get from the - * default privileges of an object to the initial privileges as recorded - * in pg_init_privs. - * - * These will then be run ahead of the regular ACL commands, which were - * calculated using the queries above, inside of a block which sets a flag - * to indicate that the backend should record the results of these GRANT - * and REVOKE statements into pg_init_privs. This is how we preserve the - * contents of that catalog across binary upgrades. - */ - if (binary_upgrade) - { - printfPQExpBuffer(init_acl_subquery, - "CASE WHEN privtype = 'e' THEN " - "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " - "(SELECT acl, row_n FROM pg_catalog.unnest(%s) " - "WITH ORDINALITY AS initp(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM " - "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) " - "AS privm(orig_acl) WHERE acl = orig_acl)) as foo) END", - initprivs_expr, - obj_kind, - acl_owner); - - 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)) " - "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, - initprivs_expr); - } - else - { - printfPQExpBuffer(init_acl_subquery, "NULL"); - printfPQExpBuffer(init_racl_subquery, "NULL"); - } -} /* * Detect whether the given GUC variable is of GUC_LIST_QUOTE type. diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index f5465f19aee..fac7a05c913 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -37,26 +37,22 @@ extern bool buildACLCommands(const char *name, const char *subname, const char *nspname, - const char *type, const char *acls, const char *racls, + const char *type, const char *acls, const char *baseacls, const char *owner, const char *prefix, int remoteVersion, PQExpBuffer sql); extern bool buildDefaultACLCommands(const char *type, const char *nspname, - const char *acls, const char *racls, - const char *initacls, const char *initracls, + const char *acls, const char *acldefault, const char *owner, int remoteVersion, PQExpBuffer sql); + +extern void quoteAclUserName(PQExpBuffer output, const char *input); + extern void buildShSecLabelQuery(const char *catalog_name, Oid objectId, PQExpBuffer sql); extern void emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer, const char *objtype, const char *objname); -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); - extern bool variable_is_guc_list_quote(const char *name); extern bool SplitGUCList(char *rawstring, char separator, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 15f55cbb19d..75ea57266e2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -179,6 +179,7 @@ static NamespaceInfo *findNamespace(Oid nsoid); static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo); static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo); static void guessConstraintInheritance(TableInfo *tblinfo, int numTables); +static void getAdditionalACLs(Archive *fout); static void dumpCommentExtended(Archive *fout, const char *type, const char *name, const char *namespace, const char *owner, CatalogId catalogId, @@ -248,8 +249,7 @@ static void dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo); static DumpId dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId, const char *type, const char *name, const char *subname, const char *nspname, const char *owner, - const char *acls, const char *racls, - const char *initacls, const char *initracls); + const DumpableAcl *dacl); static void getDependencies(Archive *fout); static void BuildArchiveDependencies(Archive *fout); @@ -888,8 +888,10 @@ main(int argc, char **argv) getDependencies(fout); /* - * Collect comments and security labels, if wanted. + * Collect ACLs, comments, and security labels, if wanted. */ + if (!dopt.aclsSkip) + getAdditionalACLs(fout); if (!dopt.no_comments) collectComments(fout); if (!dopt.no_security_labels) @@ -2859,19 +2861,18 @@ dumpDatabase(Archive *fout) i_frozenxid, i_minmxid, i_datacl, - i_rdatacl, + i_acldefault, i_datistemplate, i_datconnlimit, i_tablespace; CatalogId dbCatId; DumpId dbDumpId; + DumpableAcl dbdacl; const char *datname, *dba, *encoding, *collate, *ctype, - *datacl, - *rdatacl, *datistemplate, *datconnlimit, *tablespace; @@ -2883,40 +2884,14 @@ dumpDatabase(Archive *fout) /* * Fetch the database-level properties for this database. - * - * The order in which privileges are in the ACL string (the order they - * have been GRANT'd in, which the backend maintains) must be preserved to - * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on - * those are dumped in the correct order. Note that initial privileges - * (pg_init_privs) are not supported on databases, so this logic cannot - * make use of buildACLQueries(). */ - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90300) { appendPQExpBuffer(dbQry, "SELECT tableoid, oid, datname, " "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "datcollate, datctype, datfrozenxid, datminmxid, " - "(SELECT array_agg(acl ORDER BY row_n) FROM " - " (SELECT acl, row_n FROM " - " unnest(coalesce(datacl,acldefault('d',datdba))) " - " WITH ORDINALITY AS perm(acl,row_n) " - " WHERE NOT EXISTS ( " - " SELECT 1 " - " FROM unnest(acldefault('d',datdba)) " - " AS init(init_acl) " - " WHERE acl = init_acl)) AS datacls) " - " AS datacl, " - "(SELECT array_agg(acl ORDER BY row_n) FROM " - " (SELECT acl, row_n FROM " - " unnest(acldefault('d',datdba)) " - " WITH ORDINALITY AS initp(acl,row_n) " - " WHERE NOT EXISTS ( " - " SELECT 1 " - " FROM unnest(coalesce(datacl,acldefault('d',datdba))) " - " AS permp(orig_acl) " - " WHERE acl = orig_acl)) AS rdatacls) " - " AS rdatacl, " + "datacl, acldefault('d', datdba) AS acldefault, " "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2925,13 +2900,14 @@ dumpDatabase(Archive *fout) "WHERE datname = current_database()", username_subquery); } - else if (fout->remoteVersion >= 90300) + else if (fout->remoteVersion >= 90200) { appendPQExpBuffer(dbQry, "SELECT tableoid, oid, datname, " "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " - "datcollate, datctype, datfrozenxid, datminmxid, " - "datacl, '' as rdatacl, datistemplate, datconnlimit, " + "datcollate, datctype, datfrozenxid, 0 AS datminmxid, " + "datacl, acldefault('d', datdba) AS acldefault, " + "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2945,7 +2921,8 @@ dumpDatabase(Archive *fout) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "datcollate, datctype, datfrozenxid, 0 AS datminmxid, " - "datacl, '' as rdatacl, datistemplate, datconnlimit, " + "datacl, NULL AS acldefault, " + "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2959,7 +2936,8 @@ dumpDatabase(Archive *fout) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, " - "datacl, '' as rdatacl, datistemplate, datconnlimit, " + "datacl, NULL AS acldefault, " + "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2973,8 +2951,8 @@ dumpDatabase(Archive *fout) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, " - "datacl, '' as rdatacl, datistemplate, " - "-1 as datconnlimit, " + "datacl, NULL AS acldefault, " + "datistemplate, -1 AS datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace " "FROM pg_database " "WHERE datname = current_database()", @@ -2993,7 +2971,7 @@ dumpDatabase(Archive *fout) i_frozenxid = PQfnumber(res, "datfrozenxid"); i_minmxid = PQfnumber(res, "datminmxid"); i_datacl = PQfnumber(res, "datacl"); - i_rdatacl = PQfnumber(res, "rdatacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_datistemplate = PQfnumber(res, "datistemplate"); i_datconnlimit = PQfnumber(res, "datconnlimit"); i_tablespace = PQfnumber(res, "tablespace"); @@ -3007,8 +2985,8 @@ dumpDatabase(Archive *fout) ctype = PQgetvalue(res, 0, i_ctype); frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid)); minmxid = atooid(PQgetvalue(res, 0, i_minmxid)); - datacl = PQgetvalue(res, 0, i_datacl); - rdatacl = PQgetvalue(res, 0, i_rdatacl); + dbdacl.acl = PQgetvalue(res, 0, i_datacl); + dbdacl.acldefault = PQgetvalue(res, 0, i_acldefault); datistemplate = PQgetvalue(res, 0, i_datistemplate); datconnlimit = PQgetvalue(res, 0, i_datconnlimit); tablespace = PQgetvalue(res, 0, i_tablespace); @@ -3146,9 +3124,12 @@ dumpDatabase(Archive *fout) * Dump ACL if any. Note that we do not support initial privileges * (pg_init_privs) on databases. */ + dbdacl.privtype = 0; + dbdacl.initprivs = NULL; + dumpACL(fout, dbDumpId, InvalidDumpId, "DATABASE", qdatname, NULL, NULL, - dba, datacl, rdatacl, "", ""); + dba, &dbdacl); /* * Now construct a DATABASE PROPERTIES archive entry to restore any @@ -3470,59 +3451,30 @@ getBlobs(Archive *fout) int i_oid; int i_lomowner; int i_lomacl; - int i_rlomacl; - int i_initlomacl; - int i_initrlomacl; + int i_acldefault; pg_log_info("reading large objects"); /* Fetch BLOB OIDs, and owner/ACL data if >= 9.0 */ - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90200) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer init_acl_subquery = createPQExpBuffer(); - PQExpBuffer init_racl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery, - init_racl_subquery, "l.lomacl", "l.lomowner", - "pip.initprivs", "'L'", dopt->binary_upgrade); - appendPQExpBuffer(blobQry, - "SELECT l.oid, (%s l.lomowner) AS rolname, " - "%s AS lomacl, " - "%s AS rlomacl, " - "%s AS initlomacl, " - "%s AS initrlomacl " - "FROM pg_largeobject_metadata l " - "LEFT JOIN pg_init_privs pip ON " - "(l.oid = pip.objoid " - "AND pip.classoid = 'pg_largeobject'::regclass " - "AND pip.objsubid = 0) ", - username_subquery, - acl_subquery->data, - racl_subquery->data, - init_acl_subquery->data, - init_racl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(init_acl_subquery); - destroyPQExpBuffer(init_racl_subquery); + "SELECT oid, (%s lomowner) AS rolname, lomacl, " + "acldefault('L', lomowner) AS acldefault " + "FROM pg_largeobject_metadata", + username_subquery); } else if (fout->remoteVersion >= 90000) appendPQExpBuffer(blobQry, "SELECT oid, (%s lomowner) AS rolname, lomacl, " - "NULL AS rlomacl, NULL AS initlomacl, " - "NULL AS initrlomacl " - " FROM pg_largeobject_metadata", + "NULL AS acldefault " + "FROM pg_largeobject_metadata", username_subquery); else appendPQExpBufferStr(blobQry, "SELECT DISTINCT loid AS oid, " "NULL::name AS rolname, NULL::oid AS lomacl, " - "NULL::oid AS rlomacl, NULL::oid AS initlomacl, " - "NULL::oid AS initrlomacl " + "NULL::oid AS acldefault " " FROM pg_largeobject"); res = ExecuteSqlQuery(fout, blobQry->data, PGRES_TUPLES_OK); @@ -3530,9 +3482,7 @@ getBlobs(Archive *fout) i_oid = PQfnumber(res, "oid"); i_lomowner = PQfnumber(res, "rolname"); i_lomacl = PQfnumber(res, "lomacl"); - i_rlomacl = PQfnumber(res, "rlomacl"); - i_initlomacl = PQfnumber(res, "initlomacl"); - i_initrlomacl = PQfnumber(res, "initrlomacl"); + i_acldefault = PQfnumber(res, "acldefault"); ntups = PQntuples(res); @@ -3549,20 +3499,17 @@ getBlobs(Archive *fout) AssignDumpId(&binfo[i].dobj); binfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_oid)); + binfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_lomacl)); + binfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + binfo[i].dacl.privtype = 0; + binfo[i].dacl.initprivs = NULL; binfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_lomowner)); - binfo[i].blobacl = pg_strdup(PQgetvalue(res, i, i_lomacl)); - binfo[i].rblobacl = pg_strdup(PQgetvalue(res, i, i_rlomacl)); - binfo[i].initblobacl = pg_strdup(PQgetvalue(res, i, i_initlomacl)); - binfo[i].initrblobacl = pg_strdup(PQgetvalue(res, i, i_initrlomacl)); /* Blobs have data */ binfo[i].dobj.components |= DUMP_COMPONENT_DATA; /* Mark whether blob has an ACL */ - if (!(PQgetisnull(res, i, i_lomacl) && - PQgetisnull(res, i, i_rlomacl) && - PQgetisnull(res, i, i_initlomacl) && - PQgetisnull(res, i, i_initrlomacl))) + if (!PQgetisnull(res, i, i_lomacl)) binfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* @@ -3638,8 +3585,7 @@ dumpBlob(Archive *fout, const BlobInfo *binfo) if (binfo->dobj.dump & DUMP_COMPONENT_ACL) dumpACL(fout, binfo->dobj.dumpId, InvalidDumpId, "LARGE OBJECT", binfo->dobj.name, NULL, - NULL, binfo->rolname, binfo->blobacl, binfo->rblobacl, - binfo->initblobacl, binfo->initrblobacl); + NULL, binfo->rolname, &binfo->dacl); destroyPQExpBuffer(cquery); destroyPQExpBuffer(dquery); @@ -4999,7 +4945,6 @@ binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, NamespaceInfo * getNamespaces(Archive *fout, int *numNamespaces) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -5011,9 +4956,7 @@ getNamespaces(Archive *fout, int *numNamespaces) int i_nspowner; int i_rolname; int i_nspacl; - int i_rnspacl; - int i_initnspacl; - int i_initrnspacl; + int i_acldefault; query = createPQExpBuffer(); @@ -5021,67 +4964,18 @@ getNamespaces(Archive *fout, int *numNamespaces) * we fetch all namespaces including system ones, so that every object we * read in can be linked to a containing namespace. */ - if (fout->remoteVersion >= 90600) - { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer init_acl_subquery = createPQExpBuffer(); - PQExpBuffer init_racl_subquery = createPQExpBuffer(); - - /* - * Bypass pg_init_privs.initprivs for the public schema, for several - * reasons. First, dropping and recreating the schema detaches it - * from its pg_init_privs row, but an empty destination database - * starts with this ACL nonetheless. Second, we support dump/reload - * of public schema ownership changes. ALTER SCHEMA OWNER filters - * nspacl through aclnewowner(), but initprivs continues to reflect - * the initial owner. Hence, synthesize the value that nspacl will - * have after the restore's ALTER SCHEMA OWNER. Third, this makes the - * destination database match the source's ACL, even if the latter was - * an initdb-default ACL, which changed in v15. An upgrade pulls in - * changes to most system object ACLs that the DBA had not customized. - * We've made the public schema depart from that, because changing its - * ACL so easily breaks applications. - */ - buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery, - init_racl_subquery, "n.nspacl", "n.nspowner", - "CASE WHEN n.nspname = 'public' THEN array[" - " format('%s=UC/%s', " - " n.nspowner::regrole, n.nspowner::regrole)," - " format('=U/%s', n.nspowner::regrole)]::aclitem[] " - "ELSE pip.initprivs END", - "'n'", dopt->binary_upgrade); - + if (fout->remoteVersion >= 90200) appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, " "n.nspowner, " "(%s nspowner) AS rolname, " - "%s as nspacl, " - "%s as rnspacl, " - "%s as initnspacl, " - "%s as initrnspacl " - "FROM pg_namespace n " - "LEFT JOIN pg_init_privs pip " - "ON (n.oid = pip.objoid " - "AND pip.classoid = 'pg_namespace'::regclass " - "AND pip.objsubid = 0", - username_subquery, - acl_subquery->data, - racl_subquery->data, - init_acl_subquery->data, - init_racl_subquery->data); - - appendPQExpBufferStr(query, ") "); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(init_acl_subquery); - destroyPQExpBuffer(init_racl_subquery); - } + "n.nspacl, " + "acldefault('n', n.nspowner) AS acldefault " + "FROM pg_namespace n", + username_subquery); else appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, nspowner, " "(%s nspowner) AS rolname, " - "nspacl, NULL as rnspacl, " - "NULL AS initnspacl, NULL as initrnspacl " + "nspacl, NULL AS acldefault " "FROM pg_namespace", username_subquery); @@ -5097,9 +4991,7 @@ getNamespaces(Archive *fout, int *numNamespaces) i_nspowner = PQfnumber(res, "nspowner"); i_rolname = PQfnumber(res, "rolname"); i_nspacl = PQfnumber(res, "nspacl"); - i_rnspacl = PQfnumber(res, "rnspacl"); - i_initnspacl = PQfnumber(res, "initnspacl"); - i_initrnspacl = PQfnumber(res, "initrnspacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -5108,23 +5000,61 @@ getNamespaces(Archive *fout, int *numNamespaces) nsinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); AssignDumpId(&nsinfo[i].dobj); nsinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_nspname)); + nsinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_nspacl)); + nsinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + nsinfo[i].dacl.privtype = 0; + nsinfo[i].dacl.initprivs = NULL; nsinfo[i].nspowner = atooid(PQgetvalue(res, i, i_nspowner)); nsinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); - nsinfo[i].nspacl = pg_strdup(PQgetvalue(res, i, i_nspacl)); - nsinfo[i].rnspacl = pg_strdup(PQgetvalue(res, i, i_rnspacl)); - nsinfo[i].initnspacl = pg_strdup(PQgetvalue(res, i, i_initnspacl)); - nsinfo[i].initrnspacl = pg_strdup(PQgetvalue(res, i, i_initrnspacl)); /* Decide whether to dump this namespace */ selectDumpableNamespace(&nsinfo[i], fout); /* Mark whether namespace has an ACL */ - if (!(PQgetisnull(res, i, i_nspacl) && - PQgetisnull(res, i, i_rnspacl) && - PQgetisnull(res, i, i_initnspacl) && - PQgetisnull(res, i, i_initrnspacl))) + if (!PQgetisnull(res, i, i_nspacl)) + nsinfo[i].dobj.components |= DUMP_COMPONENT_ACL; + + /* + * We ignore any pg_init_privs.initprivs entry for the public schema + * and assume a predetermined default, for several reasons. First, + * dropping and recreating the schema removes its pg_init_privs entry, + * but an empty destination database starts with this ACL nonetheless. + * Second, we support dump/reload of public schema ownership changes. + * ALTER SCHEMA OWNER filters nspacl through aclnewowner(), but + * initprivs continues to reflect the initial owner. Hence, + * synthesize the value that nspacl will have after the restore's + * ALTER SCHEMA OWNER. Third, this makes the destination database + * match the source's ACL, even if the latter was an initdb-default + * ACL, which changed in v15. An upgrade pulls in changes to most + * system object ACLs that the DBA had not customized. We've made the + * public schema depart from that, because changing its ACL so easily + * breaks applications. + */ + if (strcmp(nsinfo[i].dobj.name, "public") == 0) + { + PQExpBuffer aclarray = createPQExpBuffer(); + PQExpBuffer aclitem = createPQExpBuffer(); + + /* Standard ACL as of v15 is {owner=UC/owner,=U/owner} */ + appendPQExpBufferChar(aclarray, '{'); + quoteAclUserName(aclitem, nsinfo[i].rolname); + appendPQExpBufferStr(aclitem, "=UC/"); + quoteAclUserName(aclitem, nsinfo[i].rolname); + appendPGArray(aclarray, aclitem->data); + resetPQExpBuffer(aclitem); + appendPQExpBufferStr(aclitem, "=U/"); + quoteAclUserName(aclitem, nsinfo[i].rolname); + appendPGArray(aclarray, aclitem->data); + appendPQExpBufferChar(aclarray, '}'); + + nsinfo[i].dacl.privtype = 'i'; + nsinfo[i].dacl.initprivs = pstrdup(aclarray->data); nsinfo[i].dobj.components |= DUMP_COMPONENT_ACL; + destroyPQExpBuffer(aclarray); + destroyPQExpBuffer(aclitem); + } + if (strlen(nsinfo[i].rolname) == 0) pg_log_warning("owner of schema \"%s\" appears to be invalid", nsinfo[i].dobj.name); @@ -5247,7 +5177,6 @@ getExtensions(Archive *fout, int *numExtensions) TypeInfo * getTypes(Archive *fout, int *numTypes) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -5259,9 +5188,7 @@ getTypes(Archive *fout, int *numTypes) int i_typname; int i_typnamespace; int i_typacl; - int i_rtypacl; - int i_inittypacl; - int i_initrtypacl; + int i_acldefault; int i_rolname; int i_typelem; int i_typrelid; @@ -5285,52 +5212,11 @@ getTypes(Archive *fout, int *numTypes) * cost of the subselect probe for all standard types. This would have to * be revisited if the backend ever allows renaming of array types. */ - - if (fout->remoteVersion >= 90600) - { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "t.typacl", "t.typowner", - "pip.initprivs", "'T'", dopt->binary_upgrade); - - appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, " - "t.typnamespace, " - "%s AS typacl, " - "%s AS rtypacl, " - "%s AS inittypacl, " - "%s AS initrtypacl, " - "(%s t.typowner) AS rolname, " - "t.typelem, t.typrelid, " - "CASE WHEN t.typrelid = 0 THEN ' '::\"char\" " - "ELSE (SELECT relkind FROM pg_class WHERE oid = t.typrelid) END AS typrelkind, " - "t.typtype, t.typisdefined, " - "t.typname[0] = '_' AND t.typelem != 0 AND " - "(SELECT typarray FROM pg_type te WHERE oid = t.typelem) = t.oid AS isarray " - "FROM pg_type t " - "LEFT JOIN pg_init_privs pip ON " - "(t.oid = pip.objoid " - "AND pip.classoid = 'pg_type'::regclass " - "AND pip.objsubid = 0) ", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, - username_subquery); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); - } - else if (fout->remoteVersion >= 90200) + if (fout->remoteVersion >= 90200) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " - "typnamespace, typacl, NULL as rtypacl, " - "NULL AS inittypacl, NULL AS initrtypacl, " + "typnamespace, typacl, " + "acldefault('T', typowner) AS acldefault, " "(%s typowner) AS rolname, " "typelem, typrelid, " "CASE WHEN typrelid = 0 THEN ' '::\"char\" " @@ -5344,8 +5230,7 @@ getTypes(Archive *fout, int *numTypes) else if (fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " - "typnamespace, NULL AS typacl, NULL as rtypacl, " - "NULL AS inittypacl, NULL AS initrtypacl, " + "typnamespace, NULL AS typacl, NULL AS acldefault, " "(%s typowner) AS rolname, " "typelem, typrelid, " "CASE WHEN typrelid = 0 THEN ' '::\"char\" " @@ -5359,8 +5244,7 @@ getTypes(Archive *fout, int *numTypes) else { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " - "typnamespace, NULL AS typacl, NULL as rtypacl, " - "NULL AS inittypacl, NULL AS initrtypacl, " + "typnamespace, NULL AS typacl, NULL AS acldefault, " "(%s typowner) AS rolname, " "typelem, typrelid, " "CASE WHEN typrelid = 0 THEN ' '::\"char\" " @@ -5382,9 +5266,7 @@ getTypes(Archive *fout, int *numTypes) i_typname = PQfnumber(res, "typname"); i_typnamespace = PQfnumber(res, "typnamespace"); i_typacl = PQfnumber(res, "typacl"); - i_rtypacl = PQfnumber(res, "rtypacl"); - i_inittypacl = PQfnumber(res, "inittypacl"); - i_initrtypacl = PQfnumber(res, "initrtypacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_rolname = PQfnumber(res, "rolname"); i_typelem = PQfnumber(res, "typelem"); i_typrelid = PQfnumber(res, "typrelid"); @@ -5402,12 +5284,12 @@ getTypes(Archive *fout, int *numTypes) tyinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_typname)); tyinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_typnamespace))); + tyinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_typacl)); + tyinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + tyinfo[i].dacl.privtype = 0; + tyinfo[i].dacl.initprivs = NULL; tyinfo[i].ftypname = NULL; /* may get filled later */ tyinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); - tyinfo[i].typacl = pg_strdup(PQgetvalue(res, i, i_typacl)); - tyinfo[i].rtypacl = pg_strdup(PQgetvalue(res, i, i_rtypacl)); - tyinfo[i].inittypacl = pg_strdup(PQgetvalue(res, i, i_inittypacl)); - tyinfo[i].initrtypacl = pg_strdup(PQgetvalue(res, i, i_initrtypacl)); tyinfo[i].typelem = atooid(PQgetvalue(res, i, i_typelem)); tyinfo[i].typrelid = atooid(PQgetvalue(res, i, i_typrelid)); tyinfo[i].typrelkind = *PQgetvalue(res, i, i_typrelkind); @@ -5433,10 +5315,7 @@ getTypes(Archive *fout, int *numTypes) selectDumpableType(&tyinfo[i], fout); /* Mark whether type has an ACL */ - if (!(PQgetisnull(res, i, i_typacl) && - PQgetisnull(res, i, i_rtypacl) && - PQgetisnull(res, i, i_inittypacl) && - PQgetisnull(res, i, i_initrtypacl))) + if (!PQgetisnull(res, i, i_typacl)) tyinfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* @@ -5963,9 +5842,7 @@ getAggregates(Archive *fout, int *numAggs) int i_proargtypes; int i_rolname; int i_aggacl; - int i_raggacl; - int i_initaggacl; - int i_initraggacl; + int i_acldefault; /* * Find all interesting aggregates. See comment in getFuncs() for the @@ -5973,16 +5850,8 @@ getAggregates(Archive *fout, int *numAggs) */ if (fout->remoteVersion >= 90600) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); const char *agg_check; - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "p.proacl", "p.proowner", - "pip.initprivs", "'f'", dopt->binary_upgrade); - agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'" : "p.proisagg"); @@ -5991,10 +5860,8 @@ getAggregates(Archive *fout, int *numAggs) "p.pronamespace AS aggnamespace, " "p.pronargs, p.proargtypes, " "(%s p.proowner) AS rolname, " - "%s AS aggacl, " - "%s AS raggacl, " - "%s AS initaggacl, " - "%s AS initraggacl " + "p.proacl AS aggacl, " + "acldefault('f', p.proowner) AS acldefault " "FROM pg_proc p " "LEFT JOIN pg_init_privs pip ON " "(p.oid = pip.objoid " @@ -6006,10 +5873,6 @@ getAggregates(Archive *fout, int *numAggs) "WHERE nspname = 'pg_catalog') OR " "p.proacl IS DISTINCT FROM pip.initprivs", username_subquery, - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, agg_check); if (dopt->binary_upgrade) appendPQExpBufferStr(query, @@ -6019,11 +5882,29 @@ getAggregates(Archive *fout, int *numAggs) "refclassid = 'pg_extension'::regclass AND " "deptype = 'e')"); appendPQExpBufferChar(query, ')'); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); + } + else if (fout->remoteVersion >= 90200) + { + appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, " + "pronamespace AS aggnamespace, " + "pronargs, proargtypes, " + "(%s proowner) AS rolname, " + "proacl AS aggacl, " + "acldefault('f', proowner) AS acldefault " + "FROM pg_proc p " + "WHERE proisagg AND (" + "pronamespace != " + "(SELECT oid FROM pg_namespace " + "WHERE nspname = 'pg_catalog')", + username_subquery); + if (dopt->binary_upgrade) + appendPQExpBufferStr(query, + " OR EXISTS(SELECT 1 FROM pg_depend WHERE " + "classid = 'pg_proc'::regclass AND " + "objid = p.oid AND " + "refclassid = 'pg_extension'::regclass AND " + "deptype = 'e')"); + appendPQExpBufferChar(query, ')'); } else if (fout->remoteVersion >= 80200) { @@ -6032,8 +5913,7 @@ getAggregates(Archive *fout, int *numAggs) "pronargs, proargtypes, " "(%s proowner) AS rolname, " "proacl AS aggacl, " - "NULL AS raggacl, " - "NULL AS initaggacl, NULL AS initraggacl " + "NULL AS acldefault " "FROM pg_proc p " "WHERE proisagg AND (" "pronamespace != " @@ -6057,8 +5937,7 @@ getAggregates(Archive *fout, int *numAggs) "proargtypes, " "(%s proowner) AS rolname, " "proacl AS aggacl, " - "NULL AS raggacl, " - "NULL AS initaggacl, NULL AS initraggacl " + "NULL AS acldefault " "FROM pg_proc " "WHERE proisagg " "AND pronamespace != " @@ -6081,9 +5960,7 @@ getAggregates(Archive *fout, int *numAggs) i_proargtypes = PQfnumber(res, "proargtypes"); i_rolname = PQfnumber(res, "rolname"); i_aggacl = PQfnumber(res, "aggacl"); - i_raggacl = PQfnumber(res, "raggacl"); - i_initaggacl = PQfnumber(res, "initaggacl"); - i_initraggacl = PQfnumber(res, "initraggacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -6094,16 +5971,16 @@ getAggregates(Archive *fout, int *numAggs) agginfo[i].aggfn.dobj.name = pg_strdup(PQgetvalue(res, i, i_aggname)); agginfo[i].aggfn.dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_aggnamespace))); + agginfo[i].aggfn.dacl.acl = pg_strdup(PQgetvalue(res, i, i_aggacl)); + agginfo[i].aggfn.dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + agginfo[i].aggfn.dacl.privtype = 0; + agginfo[i].aggfn.dacl.initprivs = NULL; agginfo[i].aggfn.rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); if (strlen(agginfo[i].aggfn.rolname) == 0) pg_log_warning("owner of aggregate function \"%s\" appears to be invalid", agginfo[i].aggfn.dobj.name); agginfo[i].aggfn.lang = InvalidOid; /* not currently interesting */ agginfo[i].aggfn.prorettype = InvalidOid; /* not saved */ - agginfo[i].aggfn.proacl = pg_strdup(PQgetvalue(res, i, i_aggacl)); - agginfo[i].aggfn.rproacl = pg_strdup(PQgetvalue(res, i, i_raggacl)); - agginfo[i].aggfn.initproacl = pg_strdup(PQgetvalue(res, i, i_initaggacl)); - agginfo[i].aggfn.initrproacl = pg_strdup(PQgetvalue(res, i, i_initraggacl)); agginfo[i].aggfn.nargs = atoi(PQgetvalue(res, i, i_pronargs)); if (agginfo[i].aggfn.nargs == 0) agginfo[i].aggfn.argtypes = NULL; @@ -6119,10 +5996,7 @@ getAggregates(Archive *fout, int *numAggs) selectDumpableObject(&(agginfo[i].aggfn.dobj), fout); /* Mark whether aggregate has an ACL */ - if (!(PQgetisnull(res, i, i_aggacl) && - PQgetisnull(res, i, i_raggacl) && - PQgetisnull(res, i, i_initaggacl) && - PQgetisnull(res, i, i_initraggacl))) + if (!PQgetisnull(res, i, i_aggacl)) agginfo[i].aggfn.dobj.components |= DUMP_COMPONENT_ACL; } @@ -6159,9 +6033,7 @@ getFuncs(Archive *fout, int *numFuncs) int i_proargtypes; int i_prorettype; int i_proacl; - int i_rproacl; - int i_initproacl; - int i_initrproacl; + int i_acldefault; /* * Find all interesting functions. This is a bit complicated: @@ -6183,30 +6055,20 @@ getFuncs(Archive *fout, int *numFuncs) * to gather the information about them, though they won't be dumped if * they are built-in. Also, in 9.6 and up, include functions in * pg_catalog if they have an ACL different from what's shown in - * pg_init_privs. + * pg_init_privs (so we have to join to pg_init_privs; annoying). */ if (fout->remoteVersion >= 90600) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); const char *not_agg_check; - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "p.proacl", "p.proowner", - "pip.initprivs", "'f'", dopt->binary_upgrade); - not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'" : "NOT p.proisagg"); appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.proname, p.prolang, " "p.pronargs, p.proargtypes, p.prorettype, " - "%s AS proacl, " - "%s AS rproacl, " - "%s AS initproacl, " - "%s AS initrproacl, " + "p.proacl, " + "acldefault('f', p.proowner) AS acldefault, " "p.pronamespace, " "(%s p.proowner) AS rolname " "FROM pg_proc p " @@ -6229,10 +6091,6 @@ getFuncs(Archive *fout, int *numFuncs) "\n WHERE pg_transform.oid > %u AND " "\n (p.oid = pg_transform.trffromsql" "\n OR p.oid = pg_transform.trftosql))", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, username_subquery, not_agg_check, g_last_builtin_oid, @@ -6247,23 +6105,23 @@ getFuncs(Archive *fout, int *numFuncs) appendPQExpBufferStr(query, "\n OR p.proacl IS DISTINCT FROM pip.initprivs"); appendPQExpBufferChar(query, ')'); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); } else { + const char *acldefault_call; + + acldefault_call = (fout->remoteVersion >= 90200 ? + "acldefault('f', proowner)" : "NULL"); + appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " "pronargs, proargtypes, prorettype, proacl, " - "NULL as rproacl, " - "NULL as initproacl, NULL AS initrproacl, " + "%s AS acldefault, " "pronamespace, " "(%s proowner) AS rolname " "FROM pg_proc p " "WHERE NOT proisagg", + acldefault_call, username_subquery); if (fout->remoteVersion >= 90200) appendPQExpBufferStr(query, @@ -6316,9 +6174,7 @@ getFuncs(Archive *fout, int *numFuncs) i_proargtypes = PQfnumber(res, "proargtypes"); i_prorettype = PQfnumber(res, "prorettype"); i_proacl = PQfnumber(res, "proacl"); - i_rproacl = PQfnumber(res, "rproacl"); - i_initproacl = PQfnumber(res, "initproacl"); - i_initrproacl = PQfnumber(res, "initrproacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -6329,13 +6185,13 @@ getFuncs(Archive *fout, int *numFuncs) finfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_proname)); finfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_pronamespace))); + finfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_proacl)); + finfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + finfo[i].dacl.privtype = 0; + finfo[i].dacl.initprivs = NULL; finfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); finfo[i].lang = atooid(PQgetvalue(res, i, i_prolang)); finfo[i].prorettype = atooid(PQgetvalue(res, i, i_prorettype)); - finfo[i].proacl = pg_strdup(PQgetvalue(res, i, i_proacl)); - finfo[i].rproacl = pg_strdup(PQgetvalue(res, i, i_rproacl)); - finfo[i].initproacl = pg_strdup(PQgetvalue(res, i, i_initproacl)); - finfo[i].initrproacl = pg_strdup(PQgetvalue(res, i, i_initrproacl)); finfo[i].nargs = atoi(PQgetvalue(res, i, i_pronargs)); if (finfo[i].nargs == 0) finfo[i].argtypes = NULL; @@ -6350,10 +6206,7 @@ getFuncs(Archive *fout, int *numFuncs) selectDumpableObject(&(finfo[i].dobj), fout); /* Mark whether function has an ACL */ - if (!(PQgetisnull(res, i, i_proacl) && - PQgetisnull(res, i, i_rproacl) && - PQgetisnull(res, i, i_initproacl) && - PQgetisnull(res, i, i_initrproacl))) + if (!PQgetisnull(res, i, i_proacl)) finfo[i].dobj.components |= DUMP_COMPONENT_ACL; if (strlen(finfo[i].rolname) == 0) @@ -6418,10 +6271,7 @@ getTables(Archive *fout, int *numTables) int i_amname; int i_is_identity_sequence; int i_relacl; - int i_rrelacl; - int i_initrelacl; - int i_initrrelacl; - int i_changed_acl; + int i_acldefault; int i_partkeydef; int i_ispartition; int i_partbound; @@ -6562,67 +6412,14 @@ getTables(Archive *fout, int *numTables) appendPQExpBufferStr(query, "false AS is_identity_sequence, "); - if (fout->remoteVersion >= 90600) - { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - PQExpBuffer attacl_subquery = createPQExpBuffer(); - PQExpBuffer attracl_subquery = createPQExpBuffer(); - PQExpBuffer attinitacl_subquery = createPQExpBuffer(); - PQExpBuffer attinitracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "c.relacl", "c.relowner", - "pip.initprivs", - "CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE) - " THEN 's' ELSE 'r' END::\"char\"", - dopt->binary_upgrade); - - buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery, - attinitracl_subquery, "at.attacl", "c.relowner", - "pip.initprivs", "'c'", dopt->binary_upgrade); - - appendPQExpBuffer(query, - "%s AS relacl, %s as rrelacl, " - "%s AS initrelacl, %s as initrrelacl, ", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data); - - appendPQExpBuffer(query, - "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON " - "(c.oid = pip.objoid " - "AND pip.classoid = 'pg_class'::regclass " - "AND pip.objsubid = at.attnum)" - "WHERE at.attrelid = c.oid AND (" - "%s IS NOT NULL " - "OR %s IS NOT NULL " - "OR %s IS NOT NULL " - "OR %s IS NOT NULL" - "))" - "AS changed_acl, ", - attacl_subquery->data, - attracl_subquery->data, - attinitacl_subquery->data, - attinitracl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); - destroyPQExpBuffer(attacl_subquery); - destroyPQExpBuffer(attracl_subquery); - destroyPQExpBuffer(attinitacl_subquery); - destroyPQExpBuffer(attinitracl_subquery); - } + if (fout->remoteVersion >= 90200) + appendPQExpBufferStr(query, + "c.relacl, " + "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE) + " THEN 's'::\"char\" ELSE 'r'::\"char\" END, c.relowner) AS acldefault, "); else appendPQExpBufferStr(query, - "c.relacl, NULL as rrelacl, " - "NULL AS initrelacl, NULL AS initrrelacl, " - "false AS changed_acl, "); + "c.relacl, NULL AS acldefault, "); if (fout->remoteVersion >= 100000) appendPQExpBufferStr(query, @@ -6644,22 +6441,16 @@ getTables(Archive *fout, int *numTables) "\nFROM pg_class c\n" "LEFT JOIN pg_depend d ON " "(c.relkind = " CppAsString2(RELKIND_SEQUENCE) " AND " - "d.classid = c.tableoid AND d.objid = c.oid AND " + "d.classid = 'pg_class'::regclass AND d.objid = c.oid AND " "d.objsubid = 0 AND " - "d.refclassid = c.tableoid AND d.deptype IN ('a', 'i'))\n" + "d.refclassid = 'pg_class'::regclass AND d.deptype IN ('a', 'i'))\n" "LEFT JOIN pg_tablespace tsp ON (tsp.oid = c.reltablespace)\n"); /* - * In 9.6 and up, left join to pg_init_privs to detect if any privileges - * are still as-set-at-init, in which case we won't dump out ACL commands - * for those. We also are interested in the amname as of 9.6. + * In 9.6 and up, left join to pg_am to pick up the amname. */ if (fout->remoteVersion >= 90600) appendPQExpBufferStr(query, - "LEFT JOIN pg_init_privs pip ON " - "(c.oid = pip.objoid " - "AND pip.classoid = 'pg_class'::regclass " - "AND pip.objsubid = 0)\n" "LEFT JOIN pg_am am ON (c.relam = am.oid)\n"); /* @@ -6671,7 +6462,9 @@ getTables(Archive *fout, int *numTables) */ if (fout->remoteVersion >= 80200) appendPQExpBufferStr(query, - "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid AND c.relkind <> " CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"); + "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid" + " AND tc.relkind = " CppAsString2(RELKIND_TOASTVALUE) + " AND c.relkind <> " CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"); /* * Restrict to interesting relkinds (in particular, not indexes). Not all @@ -6745,10 +6538,7 @@ getTables(Archive *fout, int *numTables) i_amname = PQfnumber(res, "amname"); i_is_identity_sequence = PQfnumber(res, "is_identity_sequence"); i_relacl = PQfnumber(res, "relacl"); - i_rrelacl = PQfnumber(res, "rrelacl"); - i_initrelacl = PQfnumber(res, "initrelacl"); - i_initrrelacl = PQfnumber(res, "initrrelacl"); - i_changed_acl = PQfnumber(res, "changed_acl"); + i_acldefault = PQfnumber(res, "acldefault"); i_partkeydef = PQfnumber(res, "partkeydef"); i_ispartition = PQfnumber(res, "ispartition"); i_partbound = PQfnumber(res, "partbound"); @@ -6777,6 +6567,10 @@ getTables(Archive *fout, int *numTables) tblinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_relname)); 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)); + tblinfo[i].dacl.privtype = 0; + tblinfo[i].dacl.initprivs = NULL; tblinfo[i].relkind = *(PQgetvalue(res, i, i_relkind)); tblinfo[i].reltype = atooid(PQgetvalue(res, i, i_reltype)); tblinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); @@ -6823,10 +6617,6 @@ getTables(Archive *fout, int *numTables) else tblinfo[i].amname = pg_strdup(PQgetvalue(res, i, i_amname)); tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0); - tblinfo[i].relacl = pg_strdup(PQgetvalue(res, i, i_relacl)); - tblinfo[i].rrelacl = pg_strdup(PQgetvalue(res, i, i_rrelacl)); - tblinfo[i].initrelacl = pg_strdup(PQgetvalue(res, i, i_initrelacl)); - tblinfo[i].initrrelacl = pg_strdup(PQgetvalue(res, i, i_initrrelacl)); tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef)); tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0); tblinfo[i].partbound = pg_strdup(PQgetvalue(res, i, i_partbound)); @@ -6862,23 +6652,10 @@ getTables(Archive *fout, int *numTables) /* Tables have data */ tblinfo[i].dobj.components |= DUMP_COMPONENT_DATA; - /* - * Mark whether table has an ACL. - * - * If the table-level and all column-level ACLs for this table are - * unchanged, then we don't need to worry about including the ACLs for - * this table. If any column-level ACLs have been changed, the - * 'changed_acl' column from the query will indicate that. - * - * This can result in a significant performance improvement in cases - * where we are only looking to dump out the ACL (eg: pg_catalog). - */ - if (!(PQgetisnull(res, i, i_relacl) && - PQgetisnull(res, i, i_rrelacl) && - PQgetisnull(res, i, i_initrelacl) && - PQgetisnull(res, i, i_initrrelacl) && - strcmp(PQgetvalue(res, i, i_changed_acl), "f") == 0)) + /* Mark whether table has an ACL */ + if (!PQgetisnull(res, i, i_relacl)) tblinfo[i].dobj.components |= DUMP_COMPONENT_ACL; + tblinfo[i].hascolumnACLs = false; /* may get set later */ /* * Read-lock target tables to make sure they aren't DROPPED or altered @@ -8161,7 +7938,6 @@ getEventTriggers(Archive *fout, int *numEventTriggers) ProcLangInfo * getProcLangs(Archive *fout, int *numProcLangs) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -8175,56 +7951,30 @@ getProcLangs(Archive *fout, int *numProcLangs) int i_laninline; int i_lanvalidator; int i_lanacl; - int i_rlanacl; - int i_initlanacl; - int i_initrlanacl; + int i_acldefault; int i_lanowner; - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90200) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "l.lanacl", "l.lanowner", - "pip.initprivs", "'l'", dopt->binary_upgrade); - - /* pg_language has a laninline column */ - appendPQExpBuffer(query, "SELECT l.tableoid, l.oid, " - "l.lanname, l.lanpltrusted, l.lanplcallfoid, " - "l.laninline, l.lanvalidator, " - "%s AS lanacl, " - "%s AS rlanacl, " - "%s AS initlanacl, " - "%s AS initrlanacl, " - "(%s l.lanowner) AS lanowner " - "FROM pg_language l " - "LEFT JOIN pg_init_privs pip ON " - "(l.oid = pip.objoid " - "AND pip.classoid = 'pg_language'::regclass " - "AND pip.objsubid = 0) " - "WHERE l.lanispl " - "ORDER BY l.oid", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, + /* acldefault() exists */ + appendPQExpBuffer(query, "SELECT tableoid, oid, " + "lanname, lanpltrusted, lanplcallfoid, " + "laninline, lanvalidator, " + "lanacl, " + "acldefault('l', lanowner) AS acldefault, " + "(%s lanowner) AS lanowner " + "FROM pg_language " + "WHERE lanispl " + "ORDER BY oid", username_subquery); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); } else if (fout->remoteVersion >= 90000) { /* pg_language has a laninline column */ appendPQExpBuffer(query, "SELECT tableoid, oid, " "lanname, lanpltrusted, lanplcallfoid, " - "laninline, lanvalidator, lanacl, NULL AS rlanacl, " - "NULL AS initlanacl, NULL AS initrlanacl, " + "laninline, lanvalidator, " + "lanacl, NULL AS acldefault, " "(%s lanowner) AS lanowner " "FROM pg_language " "WHERE lanispl " @@ -8237,8 +7987,7 @@ getProcLangs(Archive *fout, int *numProcLangs) appendPQExpBuffer(query, "SELECT tableoid, oid, " "lanname, lanpltrusted, lanplcallfoid, " "0 AS laninline, lanvalidator, lanacl, " - "NULL AS rlanacl, " - "NULL AS initlanacl, NULL AS initrlanacl, " + "NULL AS acldefault, " "(%s lanowner) AS lanowner " "FROM pg_language " "WHERE lanispl " @@ -8251,8 +8000,7 @@ getProcLangs(Archive *fout, int *numProcLangs) appendPQExpBuffer(query, "SELECT tableoid, oid, " "lanname, lanpltrusted, lanplcallfoid, " "0 AS laninline, lanvalidator, lanacl, " - "NULL AS rlanacl, " - "NULL AS initlanacl, NULL AS initrlanacl, " + "NULL AS acldefault, " "(%s '10') AS lanowner " "FROM pg_language " "WHERE lanispl " @@ -8265,8 +8013,7 @@ getProcLangs(Archive *fout, int *numProcLangs) appendPQExpBuffer(query, "SELECT tableoid, oid, " "lanname, lanpltrusted, lanplcallfoid, " "0 AS laninline, lanvalidator, lanacl, " - "NULL AS rlanacl, " - "NULL AS initlanacl, NULL AS initrlanacl, " + "NULL AS acldefault, " "(%s '1') AS lanowner " "FROM pg_language " "WHERE lanispl " @@ -8290,9 +8037,7 @@ getProcLangs(Archive *fout, int *numProcLangs) i_laninline = PQfnumber(res, "laninline"); i_lanvalidator = PQfnumber(res, "lanvalidator"); i_lanacl = PQfnumber(res, "lanacl"); - i_rlanacl = PQfnumber(res, "rlanacl"); - i_initlanacl = PQfnumber(res, "initlanacl"); - i_initrlanacl = PQfnumber(res, "initrlanacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_lanowner = PQfnumber(res, "lanowner"); for (i = 0; i < ntups; i++) @@ -8303,24 +8048,21 @@ getProcLangs(Archive *fout, int *numProcLangs) AssignDumpId(&planginfo[i].dobj); planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname)); + planginfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_lanacl)); + planginfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + planginfo[i].dacl.privtype = 0; + planginfo[i].dacl.initprivs = NULL; planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't'; planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid)); planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline)); planginfo[i].lanvalidator = atooid(PQgetvalue(res, i, i_lanvalidator)); - planginfo[i].lanacl = pg_strdup(PQgetvalue(res, i, i_lanacl)); - planginfo[i].rlanacl = pg_strdup(PQgetvalue(res, i, i_rlanacl)); - planginfo[i].initlanacl = pg_strdup(PQgetvalue(res, i, i_initlanacl)); - planginfo[i].initrlanacl = pg_strdup(PQgetvalue(res, i, i_initrlanacl)); planginfo[i].lanowner = pg_strdup(PQgetvalue(res, i, i_lanowner)); /* Decide whether we want to dump it */ selectDumpableProcLang(&(planginfo[i]), fout); /* Mark whether language has an ACL */ - if (!(PQgetisnull(res, i, i_lanacl) && - PQgetisnull(res, i, i_rlanacl) && - PQgetisnull(res, i, i_initlanacl) && - PQgetisnull(res, i, i_initrlanacl))) + if (!PQgetisnull(res, i, i_lanacl)) planginfo[i].dobj.components |= DUMP_COMPONENT_ACL; } @@ -9371,7 +9113,6 @@ getTSConfigurations(Archive *fout, int *numTSConfigs) FdwInfo * getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -9384,9 +9125,7 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) int i_fdwhandler; int i_fdwvalidator; int i_fdwacl; - int i_rfdwacl; - int i_initfdwacl; - int i_initrfdwacl; + int i_acldefault; int i_fdwoptions; /* Before 8.4, there are no foreign-data wrappers */ @@ -9398,46 +9137,22 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) query = createPQExpBuffer(); - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90200) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "f.fdwacl", "f.fdwowner", - "pip.initprivs", "'F'", dopt->binary_upgrade); - - appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.fdwname, " - "(%s f.fdwowner) AS rolname, " - "f.fdwhandler::pg_catalog.regproc, " - "f.fdwvalidator::pg_catalog.regproc, " - "%s AS fdwacl, " - "%s AS rfdwacl, " - "%s AS initfdwacl, " - "%s AS initrfdwacl, " + appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, " + "(%s fdwowner) AS rolname, " + "fdwhandler::pg_catalog.regproc, " + "fdwvalidator::pg_catalog.regproc, " + "fdwacl, " + "acldefault('F', fdwowner) AS acldefault, " "array_to_string(ARRAY(" "SELECT quote_ident(option_name) || ' ' || " "quote_literal(option_value) " - "FROM pg_options_to_table(f.fdwoptions) " + "FROM pg_options_to_table(fdwoptions) " "ORDER BY option_name" "), E',\n ') AS fdwoptions " - "FROM pg_foreign_data_wrapper f " - "LEFT JOIN pg_init_privs pip ON " - "(f.oid = pip.objoid " - "AND pip.classoid = 'pg_foreign_data_wrapper'::regclass " - "AND pip.objsubid = 0) ", - username_subquery, - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); + "FROM pg_foreign_data_wrapper", + username_subquery); } else if (fout->remoteVersion >= 90100) { @@ -9445,8 +9160,7 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) "(%s fdwowner) AS rolname, " "fdwhandler::pg_catalog.regproc, " "fdwvalidator::pg_catalog.regproc, fdwacl, " - "NULL as rfdwacl, " - "NULL as initfdwacl, NULL AS initrfdwacl, " + "NULL AS acldefault, " "array_to_string(ARRAY(" "SELECT quote_ident(option_name) || ' ' || " "quote_literal(option_value) " @@ -9462,8 +9176,7 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) "(%s fdwowner) AS rolname, " "'-' AS fdwhandler, " "fdwvalidator::pg_catalog.regproc, fdwacl, " - "NULL as rfdwacl, " - "NULL as initfdwacl, NULL AS initrfdwacl, " + "NULL AS acldefault, " "array_to_string(ARRAY(" "SELECT quote_ident(option_name) || ' ' || " "quote_literal(option_value) " @@ -9488,9 +9201,7 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) i_fdwhandler = PQfnumber(res, "fdwhandler"); i_fdwvalidator = PQfnumber(res, "fdwvalidator"); i_fdwacl = PQfnumber(res, "fdwacl"); - i_rfdwacl = PQfnumber(res, "rfdwacl"); - i_initfdwacl = PQfnumber(res, "initfdwacl"); - i_initrfdwacl = PQfnumber(res, "initrfdwacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_fdwoptions = PQfnumber(res, "fdwoptions"); for (i = 0; i < ntups; i++) @@ -9501,23 +9212,20 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) AssignDumpId(&fdwinfo[i].dobj); fdwinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_fdwname)); fdwinfo[i].dobj.namespace = NULL; + fdwinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_fdwacl)); + fdwinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + fdwinfo[i].dacl.privtype = 0; + fdwinfo[i].dacl.initprivs = NULL; fdwinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); fdwinfo[i].fdwhandler = pg_strdup(PQgetvalue(res, i, i_fdwhandler)); fdwinfo[i].fdwvalidator = pg_strdup(PQgetvalue(res, i, i_fdwvalidator)); fdwinfo[i].fdwoptions = pg_strdup(PQgetvalue(res, i, i_fdwoptions)); - fdwinfo[i].fdwacl = pg_strdup(PQgetvalue(res, i, i_fdwacl)); - fdwinfo[i].rfdwacl = pg_strdup(PQgetvalue(res, i, i_rfdwacl)); - fdwinfo[i].initfdwacl = pg_strdup(PQgetvalue(res, i, i_initfdwacl)); - fdwinfo[i].initrfdwacl = pg_strdup(PQgetvalue(res, i, i_initrfdwacl)); /* Decide whether we want to dump it */ selectDumpableObject(&(fdwinfo[i].dobj), fout); /* Mark whether FDW has an ACL */ - if (!(PQgetisnull(res, i, i_fdwacl) && - PQgetisnull(res, i, i_rfdwacl) && - PQgetisnull(res, i, i_initfdwacl) && - PQgetisnull(res, i, i_initrfdwacl))) + if (!PQgetisnull(res, i, i_fdwacl)) fdwinfo[i].dobj.components |= DUMP_COMPONENT_ACL; } @@ -9538,7 +9246,6 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) ForeignServerInfo * getForeignServers(Archive *fout, int *numForeignServers) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -9552,9 +9259,7 @@ getForeignServers(Archive *fout, int *numForeignServers) int i_srvtype; int i_srvversion; int i_srvacl; - int i_rsrvacl; - int i_initsrvacl; - int i_initrsrvacl; + int i_acldefault; int i_srvoptions; /* Before 8.4, there are no foreign servers */ @@ -9566,53 +9271,27 @@ getForeignServers(Archive *fout, int *numForeignServers) query = createPQExpBuffer(); - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90200) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "f.srvacl", "f.srvowner", - "pip.initprivs", "'S'", dopt->binary_upgrade); - - appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.srvname, " - "(%s f.srvowner) AS rolname, " - "f.srvfdw, f.srvtype, f.srvversion, " - "%s AS srvacl, " - "%s AS rsrvacl, " - "%s AS initsrvacl, " - "%s AS initrsrvacl, " + appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, " + "(%s srvowner) AS rolname, " + "srvfdw, srvtype, srvversion, srvacl, " + "acldefault('S', srvowner) AS acldefault, " "array_to_string(ARRAY(" "SELECT quote_ident(option_name) || ' ' || " "quote_literal(option_value) " - "FROM pg_options_to_table(f.srvoptions) " + "FROM pg_options_to_table(srvoptions) " "ORDER BY option_name" "), E',\n ') AS srvoptions " - "FROM pg_foreign_server f " - "LEFT JOIN pg_init_privs pip " - "ON (f.oid = pip.objoid " - "AND pip.classoid = 'pg_foreign_server'::regclass " - "AND pip.objsubid = 0) ", - username_subquery, - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); + "FROM pg_foreign_server", + username_subquery); } else { appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, " "(%s srvowner) AS rolname, " "srvfdw, srvtype, srvversion, srvacl, " - "NULL AS rsrvacl, " - "NULL AS initsrvacl, NULL AS initrsrvacl, " + "NULL AS acldefault, " "array_to_string(ARRAY(" "SELECT quote_ident(option_name) || ' ' || " "quote_literal(option_value) " @@ -9638,9 +9317,7 @@ getForeignServers(Archive *fout, int *numForeignServers) i_srvtype = PQfnumber(res, "srvtype"); i_srvversion = PQfnumber(res, "srvversion"); i_srvacl = PQfnumber(res, "srvacl"); - i_rsrvacl = PQfnumber(res, "rsrvacl"); - i_initsrvacl = PQfnumber(res, "initsrvacl"); - i_initrsrvacl = PQfnumber(res, "initrsrvacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_srvoptions = PQfnumber(res, "srvoptions"); for (i = 0; i < ntups; i++) @@ -9651,15 +9328,15 @@ getForeignServers(Archive *fout, int *numForeignServers) AssignDumpId(&srvinfo[i].dobj); srvinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_srvname)); srvinfo[i].dobj.namespace = NULL; + srvinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_srvacl)); + srvinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + srvinfo[i].dacl.privtype = 0; + srvinfo[i].dacl.initprivs = NULL; srvinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); srvinfo[i].srvfdw = atooid(PQgetvalue(res, i, i_srvfdw)); srvinfo[i].srvtype = pg_strdup(PQgetvalue(res, i, i_srvtype)); srvinfo[i].srvversion = pg_strdup(PQgetvalue(res, i, i_srvversion)); srvinfo[i].srvoptions = pg_strdup(PQgetvalue(res, i, i_srvoptions)); - srvinfo[i].srvacl = pg_strdup(PQgetvalue(res, i, i_srvacl)); - srvinfo[i].rsrvacl = pg_strdup(PQgetvalue(res, i, i_rsrvacl)); - srvinfo[i].initsrvacl = pg_strdup(PQgetvalue(res, i, i_initsrvacl)); - srvinfo[i].initrsrvacl = pg_strdup(PQgetvalue(res, i, i_initrsrvacl)); /* Decide whether we want to dump it */ selectDumpableObject(&(srvinfo[i].dobj), fout); @@ -9668,10 +9345,7 @@ getForeignServers(Archive *fout, int *numForeignServers) srvinfo[i].dobj.components |= DUMP_COMPONENT_USERMAP; /* Mark whether server has an ACL */ - if (!(PQgetisnull(res, i, i_srvacl) && - PQgetisnull(res, i, i_rsrvacl) && - PQgetisnull(res, i, i_initsrvacl) && - PQgetisnull(res, i, i_initrsrvacl))) + if (!PQgetisnull(res, i, i_srvacl)) srvinfo[i].dobj.components |= DUMP_COMPONENT_ACL; } @@ -9702,9 +9376,7 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) int i_defaclnamespace; int i_defaclobjtype; int i_defaclacl; - int i_rdefaclacl; - int i_initdefaclacl; - int i_initrdefaclacl; + int i_acldefault; int i, ntups; @@ -9716,13 +9388,16 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) query = createPQExpBuffer(); - if (fout->remoteVersion >= 90600) - { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); + appendPQExpBuffer(query, + "SELECT oid, tableoid, " + "(%s defaclrole) AS defaclrole, " + "defaclnamespace, " + "defaclobjtype, " + "defaclacl, ", + username_subquery); + if (fout->remoteVersion >= 90200) + { /* * Global entries (with defaclnamespace=0) replace the hard-wired * default ACL for their object type. We should dump them as deltas @@ -9730,59 +9405,24 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) * for interpreting the ALTER DEFAULT PRIVILEGES commands. On the * other hand, non-global entries can only add privileges not revoke * them. We must dump those as-is (i.e., as deltas from an empty - * ACL). We implement that by passing NULL as the object type for - * acldefault(), which works because acldefault() is STRICT. + * ACL). * * We can use defaclobjtype as the object type for acldefault(), * except for the case of 'S' (DEFACLOBJ_SEQUENCE) which must be * converted to 's'. */ - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "defaclacl", "defaclrole", - "pip.initprivs", - "CASE WHEN defaclnamespace = 0 THEN" - " CASE WHEN defaclobjtype = 'S' THEN 's'::\"char\"" - " ELSE defaclobjtype END " - "ELSE NULL END", - dopt->binary_upgrade); - - appendPQExpBuffer(query, "SELECT d.oid, d.tableoid, " - "(%s d.defaclrole) AS defaclrole, " - "d.defaclnamespace, " - "d.defaclobjtype, " - "%s AS defaclacl, " - "%s AS rdefaclacl, " - "%s AS initdefaclacl, " - "%s AS initrdefaclacl " - "FROM pg_default_acl d " - "LEFT JOIN pg_init_privs pip ON " - "(d.oid = pip.objoid " - "AND pip.classoid = 'pg_default_acl'::regclass " - "AND pip.objsubid = 0) ", - username_subquery, - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); + appendPQExpBufferStr(query, + "CASE WHEN defaclnamespace = 0 THEN " + "acldefault(CASE WHEN defaclobjtype = 'S' " + "THEN 's'::\"char\" ELSE defaclobjtype END, " + "defaclrole) ELSE '{}' END AS acldefault "); } else - { - appendPQExpBuffer(query, "SELECT oid, tableoid, " - "(%s defaclrole) AS defaclrole, " - "defaclnamespace, " - "defaclobjtype, " - "defaclacl, " - "NULL AS rdefaclacl, " - "NULL AS initdefaclacl, " - "NULL AS initrdefaclacl " - "FROM pg_default_acl", - username_subquery); - } + appendPQExpBufferStr(query, + "NULL AS acldefault "); + + appendPQExpBufferStr(query, + "FROM pg_default_acl"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -9797,9 +9437,7 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) i_defaclnamespace = PQfnumber(res, "defaclnamespace"); i_defaclobjtype = PQfnumber(res, "defaclobjtype"); i_defaclacl = PQfnumber(res, "defaclacl"); - i_rdefaclacl = PQfnumber(res, "rdefaclacl"); - i_initdefaclacl = PQfnumber(res, "initdefaclacl"); - i_initrdefaclacl = PQfnumber(res, "initrdefaclacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -9817,12 +9455,12 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) else daclinfo[i].dobj.namespace = NULL; + daclinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_defaclacl)); + daclinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + daclinfo[i].dacl.privtype = 0; + daclinfo[i].dacl.initprivs = NULL; daclinfo[i].defaclrole = pg_strdup(PQgetvalue(res, i, i_defaclrole)); daclinfo[i].defaclobjtype = *(PQgetvalue(res, i, i_defaclobjtype)); - daclinfo[i].defaclacl = pg_strdup(PQgetvalue(res, i, i_defaclacl)); - daclinfo[i].rdefaclacl = pg_strdup(PQgetvalue(res, i, i_rdefaclacl)); - daclinfo[i].initdefaclacl = pg_strdup(PQgetvalue(res, i, i_initdefaclacl)); - daclinfo[i].initrdefaclacl = pg_strdup(PQgetvalue(res, i, i_initrdefaclacl)); /* Default ACLs are ACLs, of course */ daclinfo[i].dobj.components |= DUMP_COMPONENT_ACL; @@ -9838,6 +9476,126 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) return daclinfo; } +/* + * getAdditionalACLs + * + * We have now created all the DumpableObjects, and collected the ACL data + * that appears in the directly-associated catalog entries. However, there's + * more ACL-related info to collect. If any of a table's columns have ACLs, + * we must set the TableInfo's DUMP_COMPONENT_ACL components flag, as well as + * its hascolumnACLs flag (we won't store the ACLs themselves here, though). + * Also, in versions having the pg_init_privs catalog, read that and load the + * information into the relevant DumpableObjects. + */ +static void +getAdditionalACLs(Archive *fout) +{ + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + int ntups, + i; + + /* Check for per-column ACLs */ + if (fout->remoteVersion >= 80400) + { + appendPQExpBufferStr(query, + "SELECT DISTINCT attrelid FROM pg_attribute " + "WHERE attacl IS NOT NULL"); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + for (i = 0; i < ntups; i++) + { + Oid relid = atooid(PQgetvalue(res, i, 0)); + TableInfo *tblinfo; + + tblinfo = findTableByOid(relid); + /* OK to ignore tables we haven't got a DumpableObject for */ + if (tblinfo) + { + tblinfo->dobj.components |= DUMP_COMPONENT_ACL; + tblinfo->hascolumnACLs = true; + } + } + PQclear(res); + } + + /* Fetch initial-privileges data */ + if (fout->remoteVersion >= 90600) + { + printfPQExpBuffer(query, + "SELECT objoid, classoid, objsubid, privtype, initprivs " + "FROM pg_init_privs"); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + for (i = 0; i < ntups; i++) + { + Oid objoid = atooid(PQgetvalue(res, i, 0)); + Oid classoid = atooid(PQgetvalue(res, i, 1)); + int objsubid = atoi(PQgetvalue(res, i, 2)); + char privtype = *(PQgetvalue(res, i, 3)); + char *initprivs = PQgetvalue(res, i, 4); + CatalogId objId; + DumpableObject *dobj; + + objId.tableoid = classoid; + objId.oid = objoid; + dobj = findObjectByCatalogId(objId); + /* OK to ignore entries we haven't got a DumpableObject for */ + if (dobj) + { + /* Cope with sub-object initprivs */ + if (objsubid != 0) + { + if (dobj->objType == DO_TABLE) + { + /* For a column initpriv, set the table's ACL flags */ + dobj->components |= DUMP_COMPONENT_ACL; + ((TableInfo *) dobj)->hascolumnACLs = true; + } + else + pg_log_warning("unsupported pg_init_privs entry: %u %u %d", + classoid, objoid, objsubid); + continue; + } + + /* + * We ignore any pg_init_privs.initprivs entry for the public + * schema, as explained in getNamespaces(). + */ + if (dobj->objType == DO_NAMESPACE && + strcmp(dobj->name, "public") == 0) + continue; + + /* Else it had better be of a type we think has ACLs */ + if (dobj->objType == DO_NAMESPACE || + dobj->objType == DO_TYPE || + dobj->objType == DO_FUNC || + dobj->objType == DO_AGG || + dobj->objType == DO_TABLE || + dobj->objType == DO_PROCLANG || + dobj->objType == DO_FDW || + dobj->objType == DO_FOREIGN_SERVER) + { + DumpableObjectWithAcl *daobj = (DumpableObjectWithAcl *) dobj; + + daobj->dacl.privtype = privtype; + daobj->dacl.initprivs = pstrdup(initprivs); + } + else + pg_log_warning("unsupported pg_init_privs entry: %u %u %d", + classoid, objoid, objsubid); + } + } + PQclear(res); + } + + destroyPQExpBuffer(query); +} + /* * dumpCommentExtended -- * @@ -10490,8 +10248,7 @@ dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo) if (nspinfo->dobj.dump & DUMP_COMPONENT_ACL) dumpACL(fout, nspinfo->dobj.dumpId, InvalidDumpId, "SCHEMA", qnspname, NULL, NULL, - nspinfo->rolname, nspinfo->nspacl, nspinfo->rnspacl, - nspinfo->initnspacl, nspinfo->initrnspacl); + nspinfo->rolname, &nspinfo->dacl); free(qnspname); @@ -10782,8 +10539,7 @@ dumpEnumType(Archive *fout, const TypeInfo *tyinfo) dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE", qtypname, NULL, tyinfo->dobj.namespace->dobj.name, - tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl, - tyinfo->inittypacl, tyinfo->initrtypacl); + tyinfo->rolname, &tyinfo->dacl); PQclear(res); destroyPQExpBuffer(q); @@ -10922,8 +10678,7 @@ dumpRangeType(Archive *fout, const TypeInfo *tyinfo) dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE", qtypname, NULL, tyinfo->dobj.namespace->dobj.name, - tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl, - tyinfo->inittypacl, tyinfo->initrtypacl); + tyinfo->rolname, &tyinfo->dacl); PQclear(res); destroyPQExpBuffer(q); @@ -10994,8 +10749,7 @@ dumpUndefinedType(Archive *fout, const TypeInfo *tyinfo) dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE", qtypname, NULL, tyinfo->dobj.namespace->dobj.name, - tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl, - tyinfo->inittypacl, tyinfo->initrtypacl); + tyinfo->rolname, &tyinfo->dacl); destroyPQExpBuffer(q); destroyPQExpBuffer(delq); @@ -11254,8 +11008,7 @@ dumpBaseType(Archive *fout, const TypeInfo *tyinfo) dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE", qtypname, NULL, tyinfo->dobj.namespace->dobj.name, - tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl, - tyinfo->inittypacl, tyinfo->initrtypacl); + tyinfo->rolname, &tyinfo->dacl); PQclear(res); destroyPQExpBuffer(q); @@ -11411,8 +11164,7 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE", qtypname, NULL, tyinfo->dobj.namespace->dobj.name, - tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl, - tyinfo->inittypacl, tyinfo->initrtypacl); + tyinfo->rolname, &tyinfo->dacl); /* Dump any per-constraint comments */ for (i = 0; i < tyinfo->nDomChecks; i++) @@ -11633,8 +11385,7 @@ dumpCompositeType(Archive *fout, const TypeInfo *tyinfo) dumpACL(fout, tyinfo->dobj.dumpId, InvalidDumpId, "TYPE", qtypname, NULL, tyinfo->dobj.namespace->dobj.name, - tyinfo->rolname, tyinfo->typacl, tyinfo->rtypacl, - tyinfo->inittypacl, tyinfo->initrtypacl); + tyinfo->rolname, &tyinfo->dacl); PQclear(res); destroyPQExpBuffer(q); @@ -11932,8 +11683,7 @@ dumpProcLang(Archive *fout, const ProcLangInfo *plang) if (plang->lanpltrusted && plang->dobj.dump & DUMP_COMPONENT_ACL) dumpACL(fout, plang->dobj.dumpId, InvalidDumpId, "LANGUAGE", qlanname, NULL, NULL, - plang->lanowner, plang->lanacl, plang->rlanacl, - plang->initlanacl, plang->initrlanacl); + plang->lanowner, &plang->dacl); free(qlanname); @@ -12569,8 +12319,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) dumpACL(fout, finfo->dobj.dumpId, InvalidDumpId, keyword, funcsig, NULL, finfo->dobj.namespace->dobj.name, - finfo->rolname, finfo->proacl, finfo->rproacl, - finfo->initproacl, finfo->initrproacl); + finfo->rolname, &finfo->dacl); PQclear(res); @@ -14474,9 +14223,7 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) dumpACL(fout, agginfo->aggfn.dobj.dumpId, InvalidDumpId, "FUNCTION", aggsig, NULL, agginfo->aggfn.dobj.namespace->dobj.name, - agginfo->aggfn.rolname, agginfo->aggfn.proacl, - agginfo->aggfn.rproacl, - agginfo->aggfn.initproacl, agginfo->aggfn.initrproacl); + agginfo->aggfn.rolname, &agginfo->aggfn.dacl); free(aggsig); if (aggfullsig) @@ -14875,9 +14622,7 @@ dumpForeignDataWrapper(Archive *fout, const FdwInfo *fdwinfo) if (fdwinfo->dobj.dump & DUMP_COMPONENT_ACL) dumpACL(fout, fdwinfo->dobj.dumpId, InvalidDumpId, "FOREIGN DATA WRAPPER", qfdwname, NULL, - NULL, fdwinfo->rolname, - fdwinfo->fdwacl, fdwinfo->rfdwacl, - fdwinfo->initfdwacl, fdwinfo->initrfdwacl); + NULL, fdwinfo->rolname, &fdwinfo->dacl); free(qfdwname); @@ -14964,9 +14709,7 @@ dumpForeignServer(Archive *fout, const ForeignServerInfo *srvinfo) if (srvinfo->dobj.dump & DUMP_COMPONENT_ACL) dumpACL(fout, srvinfo->dobj.dumpId, InvalidDumpId, "FOREIGN SERVER", qsrvname, NULL, - NULL, srvinfo->rolname, - srvinfo->srvacl, srvinfo->rsrvacl, - srvinfo->initsrvacl, srvinfo->initrsrvacl); + NULL, srvinfo->rolname, &srvinfo->dacl); /* Dump user mappings */ if (srvinfo->dobj.dump & DUMP_COMPONENT_USERMAP) @@ -15130,15 +14873,13 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo) if (!buildDefaultACLCommands(type, daclinfo->dobj.namespace != NULL ? daclinfo->dobj.namespace->dobj.name : NULL, - daclinfo->defaclacl, - daclinfo->rdefaclacl, - daclinfo->initdefaclacl, - daclinfo->initrdefaclacl, + daclinfo->dacl.acl, + daclinfo->dacl.acldefault, daclinfo->defaclrole, fout->remoteVersion, q)) fatal("could not parse default ACL list (%s)", - daclinfo->defaclacl); + daclinfo->dacl.acl); if (daclinfo->dobj.dump & DUMP_COMPONENT_ACL) ArchiveEntry(fout, daclinfo->dobj.catId, daclinfo->dobj.dumpId, @@ -15168,20 +14909,7 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo) * (Currently we assume that subname is only provided for table columns.) * 'nspname' is the namespace the object is in (NULL if none). * 'owner' is the owner, NULL if there is no owner (for languages). - * 'acls' contains the ACL string of the object from the appropriate system - * catalog field; it will be passed to buildACLCommands for building the - * appropriate GRANT commands. - * 'racls' contains the ACL string of any initial-but-now-revoked ACLs of the - * object; it will be passed to buildACLCommands for building the - * appropriate REVOKE commands. - * 'initacls' In binary-upgrade mode, ACL string of the object's initial - * privileges, to be recorded into pg_init_privs - * 'initracls' In binary-upgrade mode, ACL string of the object's - * revoked-from-default privileges, to be recorded into pg_init_privs - * - * NB: initacls/initracls are needed because extensions can set privileges on - * an object during the extension's script file and we record those into - * pg_init_privs as that object's initial privileges. + * 'dacl' is the DumpableAcl struct fpr the object. * * Returns the dump ID assigned to the ACL TocEntry, or InvalidDumpId if * no ACL entry was created. @@ -15191,11 +14919,15 @@ static DumpId dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId, const char *type, const char *name, const char *subname, const char *nspname, const char *owner, - const char *acls, const char *racls, - const char *initacls, const char *initracls) + const DumpableAcl *dacl) { DumpId aclDumpId = InvalidDumpId; DumpOptions *dopt = fout->dopt; + const char *acls = dacl->acl; + const char *acldefault = dacl->acldefault; + char privtype = dacl->privtype; + const char *initprivs = dacl->initprivs; + const char *baseacls; PQExpBuffer sql; /* Do nothing if ACL dump is not enabled */ @@ -15209,29 +14941,52 @@ dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId, sql = createPQExpBuffer(); /* - * Check to see if this object has had any initial ACLs included for it. - * If so, we are in binary upgrade mode and these are the ACLs to turn - * into GRANT and REVOKE statements to set and record the initial - * privileges for an extension object. Let the backend know that these - * are to be recorded by calling binary_upgrade_set_record_init_privs() - * before and after. + * In binary upgrade mode, we don't run an extension's script but instead + * dump out the objects independently and then recreate them. To preserve + * any initial privileges which were set on extension objects, we need to + * compute the set of GRANT and REVOKE commands necessary to get from the + * default privileges of an object to its initial privileges as recorded + * in pg_init_privs. + * + * At restore time, we apply these commands after having called + * binary_upgrade_set_record_init_privs(true). That tells the backend to + * copy the results into pg_init_privs. This is how we preserve the + * contents of that catalog across binary upgrades. */ - if (strlen(initacls) != 0 || strlen(initracls) != 0) + if (dopt->binary_upgrade && privtype == 'e' && + initprivs && *initprivs != '\0') { appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\n"); if (!buildACLCommands(name, subname, nspname, type, - initacls, initracls, owner, + initprivs, acldefault, owner, "", fout->remoteVersion, sql)) - fatal("could not parse initial GRANT ACL list (%s) or initial REVOKE ACL list (%s) for object \"%s\" (%s)", - initacls, initracls, name, type); + fatal("could not parse initial ACL list (%s) or default (%s) for object \"%s\" (%s)", + initprivs, acldefault, name, type); appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\n"); } + /* + * Now figure the GRANT and REVOKE commands needed to get to the object's + * actual current ACL, starting from the initprivs if given, else from the + * object-type-specific default. Also, while buildACLCommands will assume + * that a NULL/empty acls string means it needn't do anything, what that + * actually represents is the object-type-specific default; so we need to + * substitute the acldefault string to get the right results in that case. + */ + if (initprivs && *initprivs != '\0') + { + baseacls = initprivs; + if (acls == NULL || *acls == '\0') + acls = acldefault; + } + else + baseacls = acldefault; + if (!buildACLCommands(name, subname, nspname, type, - acls, racls, owner, + acls, baseacls, owner, "", fout->remoteVersion, sql)) - fatal("could not parse GRANT ACL list (%s) or REVOKE ACL list (%s) for object \"%s\" (%s)", - acls, racls, name, type); + fatal("could not parse ACL list (%s) or default (%s) for object \"%s\" (%s)", + acls, baseacls, name, type); if (sql->len > 0) { @@ -15641,8 +15396,7 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) dumpACL(fout, tbinfo->dobj.dumpId, InvalidDumpId, objtype, namecopy, NULL, tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, - tbinfo->relacl, tbinfo->rrelacl, - tbinfo->initrelacl, tbinfo->initrrelacl); + &tbinfo->dacl); } /* @@ -15651,7 +15405,7 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) * miss ACLs on system columns. Doing it this way also allows us to dump * ACLs for catalogs that we didn't mark "interesting" back in getTables. */ - if (fout->remoteVersion >= 80400 && tbinfo->dobj.dump & DUMP_COMPONENT_ACL) + if ((tbinfo->dobj.dump & DUMP_COMPONENT_ACL) && tbinfo->hascolumnACLs) { PQExpBuffer query = createPQExpBuffer(); PGresult *res; @@ -15659,55 +15413,37 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) if (fout->remoteVersion >= 90600) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "at.attacl", "c.relowner", - "pip.initprivs", "'c'", dopt->binary_upgrade); - + /* + * In principle we should call acldefault('c', relowner) to get + * the default ACL for a column. However, we don't currently + * store the numeric OID of the relowner in TableInfo. We could + * convert the owner name using regrole, but that creates a risk + * of failure due to concurrent role renames. Given that the + * default ACL for columns is empty and is likely to stay that + * way, it's not worth extra cycles and risk to avoid hard-wiring + * that knowledge here. + */ appendPQExpBuffer(query, "SELECT at.attname, " - "%s AS attacl, " - "%s AS rattacl, " - "%s AS initattacl, " - "%s AS initrattacl " + "at.attacl, " + "'{}' AS acldefault, " + "pip.privtype, pip.initprivs " "FROM pg_catalog.pg_attribute at " - "JOIN pg_catalog.pg_class c ON (at.attrelid = c.oid) " "LEFT JOIN pg_catalog.pg_init_privs pip ON " "(at.attrelid = pip.objoid " "AND pip.classoid = 'pg_catalog.pg_class'::pg_catalog.regclass " "AND at.attnum = pip.objsubid) " "WHERE at.attrelid = '%u'::pg_catalog.oid AND " "NOT at.attisdropped " - "AND (" - "%s IS NOT NULL OR " - "%s IS NOT NULL OR " - "%s IS NOT NULL OR " - "%s IS NOT NULL)" + "AND (at.attacl IS NOT NULL OR pip.initprivs IS NOT NULL) " "ORDER BY at.attnum", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, - tbinfo->dobj.catId.oid, - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); + tbinfo->dobj.catId.oid); } else { appendPQExpBuffer(query, - "SELECT attname, attacl, NULL as rattacl, " - "NULL AS initattacl, NULL AS initrattacl " + "SELECT attname, attacl, '{}' AS acldefault, " + "NULL AS privtype, NULL AS initprivs " "FROM pg_catalog.pg_attribute " "WHERE attrelid = '%u'::pg_catalog.oid AND NOT attisdropped " "AND attacl IS NOT NULL " @@ -15721,11 +15457,16 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) { char *attname = PQgetvalue(res, i, 0); char *attacl = PQgetvalue(res, i, 1); - char *rattacl = PQgetvalue(res, i, 2); - char *initattacl = PQgetvalue(res, i, 3); - char *initrattacl = PQgetvalue(res, i, 4); + char *acldefault = PQgetvalue(res, i, 2); + char privtype = *(PQgetvalue(res, i, 3)); + char *initprivs = PQgetvalue(res, i, 4); + DumpableAcl coldacl; char *attnamecopy; + coldacl.acl = attacl; + coldacl.acldefault = acldefault; + coldacl.privtype = privtype; + coldacl.initprivs = initprivs; attnamecopy = pg_strdup(fmtId(attname)); /* @@ -15736,7 +15477,7 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) dumpACL(fout, tbinfo->dobj.dumpId, tableAclDumpId, "TABLE", namecopy, attnamecopy, tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, - attacl, rattacl, initattacl, initrattacl); + &coldacl); free(attnamecopy); } PQclear(res); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 6e9a76103c8..b21b91f8bc9 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -146,16 +146,36 @@ typedef struct _dumpableObject int allocDeps; /* allocated size of dependencies[] */ } DumpableObject; +/* + * Object types that have ACLs must store them in a DumpableAcl sub-struct, + * which must immediately follow the DumpableObject base struct. + * + * Note: when dumping from a pre-9.2 server, which lacks the acldefault() + * function, acldefault will be NULL or empty. + */ +typedef struct _dumpableAcl +{ + char *acl; /* the object's actual ACL string */ + char *acldefault; /* default ACL for the object's type & owner */ + /* these fields come from the object's pg_init_privs entry, if any: */ + char privtype; /* entry type, 'i' or 'e'; 0 if no entry */ + char *initprivs; /* the object's initial ACL string, or NULL */ +} DumpableAcl; + +/* Generic struct that can be used to access any object type having an ACL */ +typedef struct _dumpableObjectWithAcl +{ + DumpableObject dobj; + DumpableAcl dacl; +} DumpableObjectWithAcl; + typedef struct _namespaceInfo { DumpableObject dobj; + DumpableAcl dacl; bool create; /* CREATE SCHEMA, or just set owner? */ Oid nspowner; char *rolname; /* name of owner, or empty string */ - char *nspacl; - char *rnspacl; - char *initnspacl; - char *initrnspacl; } NamespaceInfo; typedef struct _extensionInfo @@ -171,6 +191,7 @@ typedef struct _extensionInfo typedef struct _typeInfo { DumpableObject dobj; + DumpableAcl dacl; /* * Note: dobj.name is the raw pg_type.typname entry. ftypname is the @@ -179,10 +200,6 @@ typedef struct _typeInfo */ char *ftypname; char *rolname; /* name of owner, or empty string */ - char *typacl; - char *rtypacl; - char *inittypacl; - char *initrtypacl; Oid typelem; Oid typrelid; char typrelkind; /* 'r', 'v', 'c', etc */ @@ -207,15 +224,12 @@ typedef struct _shellTypeInfo typedef struct _funcInfo { DumpableObject dobj; + DumpableAcl dacl; char *rolname; /* name of owner, or empty string */ Oid lang; int nargs; Oid *argtypes; Oid prorettype; - char *proacl; - char *rproacl; - char *initproacl; - char *initrproacl; } FuncInfo; /* AggInfo is a superset of FuncInfo */ @@ -270,11 +284,8 @@ typedef struct _tableInfo * These fields are collected for every table in the database. */ DumpableObject dobj; + DumpableAcl dacl; char *rolname; /* name of owner, or empty string */ - char *relacl; - char *rrelacl; - char *initrelacl; - char *initrrelacl; char relkind; char relpersistence; /* relation persistence */ bool relispopulated; /* relation is populated */ @@ -286,6 +297,7 @@ typedef struct _tableInfo bool hasindex; /* does it have any indexes? */ bool hasrules; /* does it have any rules? */ bool hastriggers; /* does it have any triggers? */ + bool hascolumnACLs; /* do any columns have non-default ACLs? */ bool rowsec; /* is row security enabled? */ bool forcerowsec; /* is row security forced? */ bool hasoids; /* does it have OIDs? */ @@ -478,14 +490,11 @@ typedef struct _constraintInfo typedef struct _procLangInfo { DumpableObject dobj; + DumpableAcl dacl; bool lanpltrusted; Oid lanplcallfoid; Oid laninline; Oid lanvalidator; - char *lanacl; - char *rlanacl; - char *initlanacl; - char *initrlanacl; char *lanowner; /* name of owner, or empty string */ } ProcLangInfo; @@ -550,49 +559,37 @@ typedef struct _cfgInfo typedef struct _fdwInfo { DumpableObject dobj; + DumpableAcl dacl; char *rolname; char *fdwhandler; char *fdwvalidator; char *fdwoptions; - char *fdwacl; - char *rfdwacl; - char *initfdwacl; - char *initrfdwacl; } FdwInfo; typedef struct _foreignServerInfo { DumpableObject dobj; + DumpableAcl dacl; char *rolname; Oid srvfdw; char *srvtype; char *srvversion; - char *srvacl; - char *rsrvacl; - char *initsrvacl; - char *initrsrvacl; char *srvoptions; } ForeignServerInfo; typedef struct _defaultACLInfo { DumpableObject dobj; + DumpableAcl dacl; char *defaclrole; char defaclobjtype; - char *defaclacl; - char *rdefaclacl; - char *initdefaclacl; - char *initrdefaclacl; } DefaultACLInfo; typedef struct _blobInfo { DumpableObject dobj; + DumpableAcl dacl; char *rolname; - char *blobacl; - char *rblobacl; - char *initblobacl; - char *initrblobacl; } BlobInfo; /* diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index c29101704a5..44114f3f71d 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -1166,55 +1166,12 @@ dumpTablespaces(PGconn *conn) /* * Get all tablespaces except built-in ones (which we assume are named * pg_xxx) - * - * For the tablespace ACLs, as of 9.6, we extract both the positive (as - * spcacl) and negative (as rspcacl) ACLs, relative to the default ACL for - * tablespaces, which are then passed to buildACLCommands() below. - * - * See buildACLQueries() and buildACLCommands(). - * - * The order in which privileges are in the ACL string (the order they - * have been GRANT'd in, which the backend maintains) must be preserved to - * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on - * those are dumped in the correct order. - * - * Note that we do not support initial privileges (pg_init_privs) on - * tablespaces, so this logic cannot make use of buildACLQueries(). */ - if (server_version >= 90600) - res = executeQuery(conn, "SELECT oid, spcname, " - "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, " - "pg_catalog.pg_tablespace_location(oid), " - "(SELECT array_agg(acl ORDER BY row_n) FROM " - " (SELECT acl, row_n FROM " - " unnest(coalesce(spcacl,acldefault('t',spcowner))) " - " WITH ORDINALITY AS perm(acl,row_n) " - " WHERE NOT EXISTS ( " - " SELECT 1 " - " FROM unnest(acldefault('t',spcowner)) " - " AS init(init_acl) " - " WHERE acl = init_acl)) AS spcacls) " - " AS spcacl, " - "(SELECT array_agg(acl ORDER BY row_n) FROM " - " (SELECT acl, row_n FROM " - " unnest(acldefault('t',spcowner)) " - " WITH ORDINALITY AS initp(acl,row_n) " - " WHERE NOT EXISTS ( " - " SELECT 1 " - " FROM unnest(coalesce(spcacl,acldefault('t',spcowner))) " - " AS permp(orig_acl) " - " WHERE acl = orig_acl)) AS rspcacls) " - " AS rspcacl, " - "array_to_string(spcoptions, ', ')," - "pg_catalog.shobj_description(oid, 'pg_tablespace') " - "FROM pg_catalog.pg_tablespace " - "WHERE spcname !~ '^pg_' " - "ORDER BY 1"); - else if (server_version >= 90200) + if (server_version >= 90200) res = executeQuery(conn, "SELECT oid, spcname, " "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, " "pg_catalog.pg_tablespace_location(oid), " - "spcacl, '' as rspcacl, " + "spcacl, acldefault('t', spcowner) AS acldefault, " "array_to_string(spcoptions, ', ')," "pg_catalog.shobj_description(oid, 'pg_tablespace') " "FROM pg_catalog.pg_tablespace " @@ -1223,7 +1180,7 @@ dumpTablespaces(PGconn *conn) else if (server_version >= 90000) res = executeQuery(conn, "SELECT oid, spcname, " "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, " - "spclocation, spcacl, '' as rspcacl, " + "spclocation, spcacl, NULL AS acldefault, " "array_to_string(spcoptions, ', ')," "pg_catalog.shobj_description(oid, 'pg_tablespace') " "FROM pg_catalog.pg_tablespace " @@ -1232,7 +1189,7 @@ dumpTablespaces(PGconn *conn) else if (server_version >= 80200) res = executeQuery(conn, "SELECT oid, spcname, " "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, " - "spclocation, spcacl, '' as rspcacl, null, " + "spclocation, spcacl, NULL AS acldefault, null, " "pg_catalog.shobj_description(oid, 'pg_tablespace') " "FROM pg_catalog.pg_tablespace " "WHERE spcname !~ '^pg_' " @@ -1240,7 +1197,7 @@ dumpTablespaces(PGconn *conn) else res = executeQuery(conn, "SELECT oid, spcname, " "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, " - "spclocation, spcacl, '' as rspcacl, " + "spclocation, spcacl, NULL AS acldefault, " "null, null " "FROM pg_catalog.pg_tablespace " "WHERE spcname !~ '^pg_' " @@ -1257,7 +1214,7 @@ dumpTablespaces(PGconn *conn) char *spcowner = PQgetvalue(res, i, 2); char *spclocation = PQgetvalue(res, i, 3); char *spcacl = PQgetvalue(res, i, 4); - char *rspcacl = PQgetvalue(res, i, 5); + char *acldefault = PQgetvalue(res, i, 5); char *spcoptions = PQgetvalue(res, i, 6); char *spccomment = PQgetvalue(res, i, 7); char *fspcname; @@ -1276,9 +1233,11 @@ dumpTablespaces(PGconn *conn) appendPQExpBuffer(buf, "ALTER TABLESPACE %s SET (%s);\n", fspcname, spcoptions); + /* tablespaces can't have initprivs */ + if (!skip_acls && !buildACLCommands(fspcname, NULL, NULL, "TABLESPACE", - spcacl, rspcacl, + spcacl, acldefault, spcowner, "", server_version, buf)) { pg_log_error("could not parse ACL list (%s) for tablespace \"%s\"", diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c index 3efee4e7eed..81e623602ec 100644 --- a/src/fe_utils/string_utils.c +++ b/src/fe_utils/string_utils.c @@ -726,6 +726,69 @@ parsePGArray(const char *atext, char ***itemarray, int *nitems) } +/* + * Append one element to the text representation of a 1-dimensional Postgres + * array. + * + * The caller must provide the initial '{' and closing '}' of the array. + * This function handles all else, including insertion of commas and + * quoting of values. + * + * We assume that typdelim is ','. + */ +void +appendPGArray(PQExpBuffer buffer, const char *value) +{ + bool needquote; + const char *tmp; + + if (buffer->data[buffer->len - 1] != '{') + appendPQExpBufferChar(buffer, ','); + + /* Decide if we need quotes; this should match array_out()'s choices. */ + if (value[0] == '\0') + needquote = true; /* force quotes for empty string */ + else if (pg_strcasecmp(value, "NULL") == 0) + needquote = true; /* force quotes for literal NULL */ + else + needquote = false; + + if (!needquote) + { + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '{' || ch == '}' || ch == ',' || + /* these match array_isspace(): */ + ch == ' ' || ch == '\t' || ch == '\n' || + ch == '\r' || ch == '\v' || ch == '\f') + { + needquote = true; + break; + } + } + } + + if (needquote) + { + appendPQExpBufferChar(buffer, '"'); + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendPQExpBufferChar(buffer, '\\'); + appendPQExpBufferChar(buffer, ch); + } + appendPQExpBufferChar(buffer, '"'); + } + else + appendPQExpBufferStr(buffer, value); +} + + /* * Format a reloptions array and append it to the given buffer. * diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h index caafb97d293..e12e61cddb3 100644 --- a/src/include/fe_utils/string_utils.h +++ b/src/include/fe_utils/string_utils.h @@ -46,6 +46,7 @@ extern void appendConnStrVal(PQExpBuffer buf, const char *str); extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname); extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems); +extern void appendPGArray(PQExpBuffer buffer, const char *value); extern bool appendReloptionsArray(PQExpBuffer buffer, const char *reloptions, const char *prefix, int encoding, bool std_strings); -- 2.30.2