Re: Assorted improvements in pg_dump - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Assorted improvements in pg_dump |
Date | |
Msg-id | 3780613.1635109855@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Assorted improvements in pg_dump (Hans Buschmann <buschmann@nidsa.net>) |
Responses |
Re: Assorted improvements in pg_dump
AW: Assorted improvements in pg_dump Re: Assorted improvements in pg_dump AW: Assorted improvements in pg_dump |
List | pgsql-hackers |
Hans Buschmann <buschmann@nidsa.net> writes: > Some time ago I experimented with a customer database dump in parallel directory mode -F directory -j (2-4) > I noticed it took quite long to complete. > Further investigation showed that in this mode with multiple jobs the tables are processed in decreasing size order, whichmakes sense to avoid a long tail of a big table in one of the jobs prolonging overall dump time. > Exactly one table took very long, but seemed to be of moderate size. > But the size-determination fails to consider the size of toast tables and this table had a big associated toast-table ofbytea column(s). Hmm, yeah, we just use pg_class.relpages for scheduling parallel dumps. I'd supposed that would be fine, but maybe it's worth being smarter. I think it should be sufficient to add on the toast table's relpages value; that's maintained by autovacuum on the same terms as relpages for regular tables. See 0005 below. Here's an update of this patch series: 0001 is the same as before, except I changed collectComments and collectSecLabels to strdup the strings they want and then release their PGresults. The previous behavior confused valgrind's leak tracking, which is only a minor annoyance, but I think we can justify changing it now that these functions don't save all of the collected comments or seclabels. In particular, we've got no need for the several thousand comments on built-in objects, so that that PGresult is at least 100KB bigger than what we're going to keep. 0002 is updated to account for commit 2acc84c6f. 0003 is the same except I added a missing free(). 0004 is a new patch based on an idea from Andres Freund [1]: in the functions that repetitively issue the same query against different tables, issue just one query and use a WHERE clause to restrict the output to the tables we care about. I was skeptical about this to start with, but it turns out to be quite a spectacular win. On my machine, the time to pg_dump the regression database (with "-s") drops from 0.91 seconds to 0.39 seconds. For a database with 10000 toy tables, the time drops from 18.1 seconds to 2.3 seconds. 0004 is not committable as-is, because it assumes that the source server has single-array unnest(), which is not true before 8.4. We could fix that by using "oid = ANY(array-constant)" conditions instead, but I'm unsure about the performance properties of that for large OID arrays on those old server versions. There's a discussion at [2] about whether it'd be okay to drop pg_dump's support for pre-8.4 servers; if we do so, then it would become unnecessary to do anything more here. 0005 implements your suggestion of accounting for TOAST data while scheduling parallel dumps. I realized while looking at that that there's a pre-existing bug, which this'd exacerbate: on machines with 32-bit off_t, dataLength can overflow. Admittedly such machines are just about extinct in the wild, but we do still trouble to support the case. So 0005 also includes code to check for overflow and clamp the result to INT_MAX blocks. Maybe we should back-patch 0005. OTOH, how likely is it that anyone is wrangling tables exceeding 16TB on a machine with 32-bit off_t? Or that poor parallel dump scheduling would be a real problem in such a case? Lastly, 0006 implements the other idea we'd discussed in the other thread: for queries that are issued repetitively but not within a single pg_dump function invocation, use PREPARE/EXECUTE to cut down the overhead. This gets only diminishing returns after 0004, but it still brings "pg_dump -s regression" down from 0.39s to 0.33s, so maybe it's worth doing. I stopped after caching the plans for functions/aggregates/operators/types, though. The remaining sorts of objects aren't likely to appear in typical databases enough times to be worth worrying over. (This patch will be a net loss if there are more than zero but less than perhaps 10 instances of an object type, so there's definitely reasons beyond laziness for not doing more.) regards, tom lane [1] https://www.postgresql.org/message-id/20211022055939.z6fihsm7hdzbjttf%40alap3.anarazel.de [2] https://www.postgresql.org/message-id/flat/2923349.1634942313%40sss.pgh.pa.us diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index e38ff3c030..310f94605c 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -581,6 +581,8 @@ AssignDumpId(DumpableObject *dobj) dobj->namespace = NULL; /* may be set later */ dobj->dump = DUMP_COMPONENT_ALL; /* default assumption */ dobj->dump_contains = DUMP_COMPONENT_ALL; /* default assumption */ + /* All objects have definitions; we may set more components bits later */ + dobj->components = DUMP_COMPONENT_DEFINITION; dobj->ext_member = false; /* default assumption */ dobj->depends_on_ext = false; /* default assumption */ dobj->dependencies = NULL; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e9f68e8986..e5ea2c082d 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -134,6 +134,14 @@ static const CatalogId nilCatalogId = {0, 0}; static bool have_extra_float_digits = false; static int extra_float_digits; +/* sorted table of comments */ +static CommentItem *comments = NULL; +static int ncomments = 0; + +/* sorted table of security labels */ +static SecLabelItem *seclabels = NULL; +static int nseclabels = 0; + /* * The default number of rows per INSERT when * --inserts is specified without --rows-per-insert @@ -182,14 +190,14 @@ static inline void dumpComment(Archive *fout, const char *type, int subid, DumpId dumpId); static int findComments(Archive *fout, Oid classoid, Oid objoid, CommentItem **items); -static int collectComments(Archive *fout, CommentItem **items); +static void collectComments(Archive *fout); static void dumpSecLabel(Archive *fout, const char *type, const char *name, const char *namespace, const char *owner, CatalogId catalogId, int subid, DumpId dumpId); static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, SecLabelItem **items); -static int collectSecLabels(Archive *fout, SecLabelItem **items); -static void dumpDumpableObject(Archive *fout, const DumpableObject *dobj); +static void collectSecLabels(Archive *fout); +static void dumpDumpableObject(Archive *fout, DumpableObject *dobj); static void dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo); static void dumpExtension(Archive *fout, const ExtensionInfo *extinfo); static void dumpType(Archive *fout, const TypeInfo *tyinfo); @@ -290,8 +298,9 @@ static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout, Oid pg_type_oid, bool force_array_type, bool include_multirange_type); -static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout, - PQExpBuffer upgrade_buffer, Oid pg_rel_oid); +static void binary_upgrade_set_type_oids_by_rel(Archive *fout, + PQExpBuffer upgrade_buffer, + const TableInfo *tbinfo); static void binary_upgrade_set_pg_class_oids(Archive *fout, PQExpBuffer upgrade_buffer, Oid pg_class_oid, bool is_index); @@ -878,6 +887,14 @@ main(int argc, char **argv) */ getDependencies(fout); + /* + * Collect comments and security labels, if wanted. + */ + if (!dopt.no_comments) + collectComments(fout); + if (!dopt.no_security_labels) + collectSecLabels(fout); + /* Lastly, create dummy objects to represent the section boundaries */ boundaryObjs = createBoundaryObjects(); @@ -1631,6 +1648,13 @@ selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout) if (nsinfo->nspowner == ROLE_PG_DATABASE_OWNER) nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION; nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL; + + /* + * Also, make like it has a comment even if it doesn't; this is so + * that we'll emit a command to drop the comment, if appropriate. + * (Without this, we'd not call dumpCommentExtended for it.) + */ + nsinfo->dobj.components |= DUMP_COMPONENT_COMMENT; } else nsinfo->dobj.dump_contains = nsinfo->dobj.dump = DUMP_COMPONENT_ALL; @@ -2471,7 +2495,7 @@ getTableData(DumpOptions *dopt, TableInfo *tblinfo, int numTables, char relkind) * Make a dumpable object for the data of this specific table * * Note: we make a TableDataInfo if and only if we are going to dump the - * table data; the "dump" flag in such objects isn't used. + * table data; the "dump" field in such objects isn't very interesting. */ static void makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo) @@ -2531,6 +2555,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo) tdinfo->filtercond = NULL; /* might get set later */ addObjectDependency(&tdinfo->dobj, tbinfo->dobj.dumpId); + /* A TableDataInfo contains data, of course */ + tdinfo->dobj.components |= DUMP_COMPONENT_DATA; + tbinfo->dataObj = tdinfo; /* Make sure that we'll collect per-column info for this table. */ @@ -3491,11 +3518,15 @@ getBlobs(Archive *fout) binfo[i].initblobacl = pg_strdup(PQgetvalue(res, i, i_initlomacl)); binfo[i].initrblobacl = pg_strdup(PQgetvalue(res, i, i_initrlomacl)); - if (PQgetisnull(res, i, i_lomacl) && - PQgetisnull(res, i, i_rlomacl) && - PQgetisnull(res, i, i_initlomacl) && - PQgetisnull(res, i, i_initrlomacl)) - binfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + binfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* * In binary-upgrade mode for blobs, we do *not* dump out the blob @@ -3519,6 +3550,7 @@ getBlobs(Archive *fout) bdata->catId = nilCatalogId; AssignDumpId(bdata); bdata->name = pg_strdup("BLOBS"); + bdata->components |= DUMP_COMPONENT_DATA; } PQclear(res); @@ -3566,7 +3598,7 @@ dumpBlob(Archive *fout, const BlobInfo *binfo) binfo->dobj.catId, 0, binfo->dobj.dumpId); /* Dump ACL if any */ - if (binfo->blobacl && (binfo->dobj.dump & DUMP_COMPONENT_ACL)) + 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, @@ -3697,6 +3729,8 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) if (tbinfo->rowsec) { + tbinfo->dobj.components |= DUMP_COMPONENT_POLICY; + /* * Note: use tableoid 0 so that this object won't be mistaken for * something that pg_depend entries apply to. @@ -3766,6 +3800,8 @@ getPolicies(Archive *fout, TableInfo tblinfo[], int numTables) if (!(tbinfo->dobj.dump & DUMP_COMPONENT_POLICY)) continue; + tbinfo->dobj.components |= DUMP_COMPONENT_POLICY; + polinfo[j].dobj.objType = DO_POLICY; polinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid)); @@ -3818,6 +3854,7 @@ dumpPolicy(Archive *fout, const PolicyInfo *polinfo) const char *cmd; char *tag; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -3838,7 +3875,7 @@ dumpPolicy(Archive *fout, const PolicyInfo *polinfo) * explicitly, because it will not match anything in pg_depend (unlike * the case for other PolicyInfo objects). */ - if (polinfo->dobj.dump & DUMP_COMPONENT_POLICY) + if (polinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) ArchiveEntry(fout, polinfo->dobj.catId, polinfo->dobj.dumpId, ARCHIVE_OPTS(.tag = polinfo->dobj.name, .namespace = polinfo->dobj.namespace->dobj.name, @@ -3900,7 +3937,7 @@ dumpPolicy(Archive *fout, const PolicyInfo *polinfo) tag = psprintf("%s %s", tbinfo->dobj.name, polinfo->dobj.name); - if (polinfo->dobj.dump & DUMP_COMPONENT_POLICY) + if (polinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) ArchiveEntry(fout, polinfo->dobj.catId, polinfo->dobj.dumpId, ARCHIVE_OPTS(.tag = tag, .namespace = polinfo->dobj.namespace->dobj.name, @@ -4045,9 +4082,6 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) char *qpubname; bool first = true; - if (!(pubinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)) - return; - delq = createPQExpBuffer(); query = createPQExpBuffer(); @@ -4103,13 +4137,14 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) appendPQExpBufferStr(query, ");\n"); - ArchiveEntry(fout, pubinfo->dobj.catId, pubinfo->dobj.dumpId, - ARCHIVE_OPTS(.tag = pubinfo->dobj.name, - .owner = pubinfo->rolname, - .description = "PUBLICATION", - .section = SECTION_POST_DATA, - .createStmt = query->data, - .dropStmt = delq->data)); + if (pubinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + ArchiveEntry(fout, pubinfo->dobj.catId, pubinfo->dobj.dumpId, + ARCHIVE_OPTS(.tag = pubinfo->dobj.name, + .owner = pubinfo->rolname, + .description = "PUBLICATION", + .section = SECTION_POST_DATA, + .createStmt = query->data, + .dropStmt = delq->data)); if (pubinfo->dobj.dump & DUMP_COMPONENT_COMMENT) dumpComment(fout, "PUBLICATION", qpubname, @@ -4225,9 +4260,6 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) PQExpBuffer query; char *tag; - if (!(pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)) - return; - tag = psprintf("%s %s", pubinfo->dobj.name, tbinfo->dobj.name); query = createPQExpBuffer(); @@ -4244,13 +4276,14 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo) * owner field anyway to ensure that the command is run by the correct * role at restore time. */ - ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId, - ARCHIVE_OPTS(.tag = tag, - .namespace = tbinfo->dobj.namespace->dobj.name, - .owner = pubinfo->rolname, - .description = "PUBLICATION TABLE", - .section = SECTION_POST_DATA, - .createStmt = query->data)); + if (pubrinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + ArchiveEntry(fout, pubrinfo->dobj.catId, pubrinfo->dobj.dumpId, + ARCHIVE_OPTS(.tag = tag, + .namespace = tbinfo->dobj.namespace->dobj.name, + .owner = pubinfo->rolname, + .description = "PUBLICATION TABLE", + .section = SECTION_POST_DATA, + .createStmt = query->data)); free(tag); destroyPQExpBuffer(query); @@ -4420,9 +4453,6 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo) int i; char two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'}; - if (!(subinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)) - return; - delq = createPQExpBuffer(); query = createPQExpBuffer(); @@ -4468,13 +4498,14 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo) appendPQExpBufferStr(query, ");\n"); - ArchiveEntry(fout, subinfo->dobj.catId, subinfo->dobj.dumpId, - ARCHIVE_OPTS(.tag = subinfo->dobj.name, - .owner = subinfo->rolname, - .description = "SUBSCRIPTION", - .section = SECTION_POST_DATA, - .createStmt = query->data, - .dropStmt = delq->data)); + if (subinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + ArchiveEntry(fout, subinfo->dobj.catId, subinfo->dobj.dumpId, + ARCHIVE_OPTS(.tag = subinfo->dobj.name, + .owner = subinfo->rolname, + .description = "SUBSCRIPTION", + .section = SECTION_POST_DATA, + .createStmt = query->data, + .dropStmt = delq->data)); if (subinfo->dobj.dump & DUMP_COMPONENT_COMMENT) dumpComment(fout, "SUBSCRIPTION", qsubname, @@ -4662,30 +4693,15 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout, } static void -binary_upgrade_set_type_oids_by_rel_oid(Archive *fout, - PQExpBuffer upgrade_buffer, - Oid pg_rel_oid) +binary_upgrade_set_type_oids_by_rel(Archive *fout, + PQExpBuffer upgrade_buffer, + const TableInfo *tbinfo) { - PQExpBuffer upgrade_query = createPQExpBuffer(); - PGresult *upgrade_res; - Oid pg_type_oid; - - appendPQExpBuffer(upgrade_query, - "SELECT c.reltype AS crel " - "FROM pg_catalog.pg_class c " - "WHERE c.oid = '%u'::pg_catalog.oid;", - pg_rel_oid); - - upgrade_res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data); - - pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel"))); + Oid pg_type_oid = tbinfo->reltype; if (OidIsValid(pg_type_oid)) binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer, pg_type_oid, false, false); - - PQclear(upgrade_res); - destroyPQExpBuffer(upgrade_query); } static void @@ -4940,18 +4956,12 @@ getNamespaces(Archive *fout, int *numNamespaces) /* Decide whether to dump this namespace */ selectDumpableNamespace(&nsinfo[i], fout); - /* - * Do not try to dump ACL if the ACL is empty or the default. - * - * This is useful because, for some schemas/objects, the only - * component we are going to try and dump is the ACL and if we can - * remove that then 'dump' goes to zero/false and we don't consider - * this object for dumping at all later on. - */ - if (PQgetisnull(res, i, i_nspacl) && PQgetisnull(res, i, i_rnspacl) && - PQgetisnull(res, i, i_initnspacl) && - PQgetisnull(res, i, i_initrnspacl)) - nsinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + nsinfo[i].dobj.components |= DUMP_COMPONENT_ACL; if (strlen(nsinfo[i].rolname) == 0) pg_log_warning("owner of schema \"%s\" appears to be invalid", @@ -5260,11 +5270,12 @@ getTypes(Archive *fout, int *numTypes) /* Decide whether we want to dump it */ selectDumpableType(&tyinfo[i], fout); - /* Do not try to dump ACL if no ACL exists. */ - if (PQgetisnull(res, i, i_typacl) && PQgetisnull(res, i, i_rtypacl) && - PQgetisnull(res, i, i_inittypacl) && - PQgetisnull(res, i, i_initrtypacl)) - tyinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + tyinfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* * If it's a domain, fetch info about its constraints, if any @@ -5387,9 +5398,6 @@ getOperators(Archive *fout, int *numOprs) /* Decide whether we want to dump it */ selectDumpableObject(&(oprinfo[i].dobj), fout); - /* Operators do not currently have ACLs. */ - oprinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; - if (strlen(oprinfo[i].rolname) == 0) pg_log_warning("owner of operator \"%s\" appears to be invalid", oprinfo[i].dobj.name); @@ -5469,9 +5477,6 @@ getCollations(Archive *fout, int *numCollations) /* Decide whether we want to dump it */ selectDumpableObject(&(collinfo[i].dobj), fout); - - /* Collations do not currently have ACLs. */ - collinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -5541,9 +5546,6 @@ getConversions(Archive *fout, int *numConversions) /* Decide whether we want to dump it */ selectDumpableObject(&(convinfo[i].dobj), fout); - - /* Conversions do not currently have ACLs. */ - convinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -5614,9 +5616,6 @@ getAccessMethods(Archive *fout, int *numAccessMethods) /* Decide whether we want to dump it */ selectDumpableAccessMethod(&(aminfo[i]), fout); - - /* Access methods do not currently have ACLs. */ - aminfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -5686,9 +5685,6 @@ getOpclasses(Archive *fout, int *numOpclasses) /* Decide whether we want to dump it */ selectDumpableObject(&(opcinfo[i].dobj), fout); - /* Op Classes do not currently have ACLs. */ - opcinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; - if (strlen(opcinfo[i].rolname) == 0) pg_log_warning("owner of operator class \"%s\" appears to be invalid", opcinfo[i].dobj.name); @@ -5769,9 +5765,6 @@ getOpfamilies(Archive *fout, int *numOpfamilies) /* Decide whether we want to dump it */ selectDumpableObject(&(opfinfo[i].dobj), fout); - /* Extensions do not currently have ACLs. */ - opfinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; - if (strlen(opfinfo[i].rolname) == 0) pg_log_warning("owner of operator family \"%s\" appears to be invalid", opfinfo[i].dobj.name); @@ -5963,11 +5956,12 @@ getAggregates(Archive *fout, int *numAggs) /* Decide whether we want to dump it */ selectDumpableObject(&(agginfo[i].aggfn.dobj), fout); - /* Do not try to dump ACL if no ACL exists. */ - if (PQgetisnull(res, i, i_aggacl) && PQgetisnull(res, i, i_raggacl) && - PQgetisnull(res, i, i_initaggacl) && - PQgetisnull(res, i, i_initraggacl)) - agginfo[i].aggfn.dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + agginfo[i].aggfn.dobj.components |= DUMP_COMPONENT_ACL; } PQclear(res); @@ -6193,11 +6187,12 @@ getFuncs(Archive *fout, int *numFuncs) /* Decide whether we want to dump it */ selectDumpableObject(&(finfo[i].dobj), fout); - /* Do not try to dump ACL if no ACL exists. */ - if (PQgetisnull(res, i, i_proacl) && PQgetisnull(res, i, i_rproacl) && - PQgetisnull(res, i, i_initproacl) && - PQgetisnull(res, i, i_initrproacl)) - finfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + finfo[i].dobj.components |= DUMP_COMPONENT_ACL; if (strlen(finfo[i].rolname) == 0) pg_log_warning("owner of function \"%s\" appears to be invalid", @@ -6232,6 +6227,7 @@ getTables(Archive *fout, int *numTables) int i_relname; int i_relnamespace; int i_relkind; + int i_reltype; int i_rolname; int i_relchecks; int i_relhasindex; @@ -6282,7 +6278,7 @@ getTables(Archive *fout, int *numTables) appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, c.relname, " - "c.relnamespace, c.relkind, " + "c.relnamespace, c.relkind, c.reltype, " "(%s c.relowner) AS rolname, " "c.relchecks, " "c.relhasindex, c.relhasrules, c.relpages, " @@ -6558,6 +6554,7 @@ getTables(Archive *fout, int *numTables) i_relname = PQfnumber(res, "relname"); i_relnamespace = PQfnumber(res, "relnamespace"); i_relkind = PQfnumber(res, "relkind"); + i_reltype = PQfnumber(res, "reltype"); i_rolname = PQfnumber(res, "rolname"); i_relchecks = PQfnumber(res, "relchecks"); i_relhasindex = PQfnumber(res, "relhasindex"); @@ -6619,6 +6616,7 @@ getTables(Archive *fout, int *numTables) tblinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_relnamespace))); 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)); tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks)); tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0); @@ -6681,11 +6679,30 @@ getTables(Archive *fout, int *numTables) else selectDumpableTable(&tblinfo[i], fout); - tblinfo[i].interesting = tblinfo[i].dobj.dump ? true : false; + /* + * Now, consider the table "interesting" if we need to dump its + * definition or its data. Later on, we'll skip a lot of data + * collection for uninteresting tables. + * + * Note: the "interesting" flag will also be set by flagInhTables for + * parents of interesting tables, so that we collect necessary + * inheritance info even when the parents are not themselves being + * dumped. This is the main reason why we need an "interesting" flag + * that's separate from the components-to-dump bitmask. + */ + tblinfo[i].interesting = (tblinfo[i].dobj.dump & + (DUMP_COMPONENT_DEFINITION | + DUMP_COMPONENT_DATA)) != 0; + tblinfo[i].dummy_view = false; /* might get set during sort */ tblinfo[i].postponed_def = false; /* might get set during sort */ + /* 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 @@ -6694,11 +6711,12 @@ getTables(Archive *fout, int *numTables) * 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) - tblinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + 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)) + tblinfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* * Read-lock target tables to make sure they aren't DROPPED or altered @@ -6715,10 +6733,9 @@ getTables(Archive *fout, int *numTables) * We only need to lock the table for certain components; see * pg_dump.h */ - if (tblinfo[i].dobj.dump && + if ((tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK) && (tblinfo[i].relkind == RELKIND_RELATION || - tblinfo[i].relkind == RELKIND_PARTITIONED_TABLE) && - (tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK)) + tblinfo[i].relkind == RELKIND_PARTITIONED_TABLE)) { resetPQExpBuffer(query); appendPQExpBuffer(query, @@ -6900,13 +6917,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) continue; /* - * Ignore indexes of tables whose definitions are not to be dumped. - * - * We also need indexes on partitioned tables which have partitions to - * be dumped, in order to dump the indexes on the partitions. + * We can ignore indexes of uninteresting tables. */ - if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) && - !tbinfo->interesting) + if (!tbinfo->interesting) continue; pg_log_info("reading indexes for table \"%s.%s\"", @@ -7276,9 +7289,6 @@ getExtendedStatistics(Archive *fout) /* Decide whether we want to dump it */ selectDumpableObject(&(statsextinfo[i].dobj), fout); - - /* Stats objects do not currently have ACLs. */ - statsextinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -7968,9 +7978,6 @@ getEventTriggers(Archive *fout, int *numEventTriggers) /* Decide whether we want to dump it */ selectDumpableObject(&(evtinfo[i].dobj), fout); - - /* Event Triggers do not currently have ACLs. */ - evtinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -8147,11 +8154,12 @@ getProcLangs(Archive *fout, int *numProcLangs) /* Decide whether we want to dump it */ selectDumpableProcLang(&(planginfo[i]), fout); - /* Do not try to dump ACL if no ACL exists. */ - if (PQgetisnull(res, i, i_lanacl) && PQgetisnull(res, i, i_rlanacl) && - PQgetisnull(res, i, i_initlanacl) && - PQgetisnull(res, i, i_initrlanacl)) - planginfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + planginfo[i].dobj.components |= DUMP_COMPONENT_ACL; } PQclear(res); @@ -8261,9 +8269,6 @@ getCasts(Archive *fout, int *numCasts) /* Decide whether we want to dump it */ selectDumpableCast(&(castinfo[i]), fout); - - /* Casts do not currently have ACLs. */ - castinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -8960,9 +8965,6 @@ getTSParsers(Archive *fout, int *numTSParsers) /* Decide whether we want to dump it */ selectDumpableObject(&(prsinfo[i].dobj), fout); - - /* Text Search Parsers do not currently have ACLs. */ - prsinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -9043,9 +9045,6 @@ getTSDictionaries(Archive *fout, int *numTSDicts) /* Decide whether we want to dump it */ selectDumpableObject(&(dictinfo[i].dobj), fout); - - /* Text Search Dictionaries do not currently have ACLs. */ - dictinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -9118,9 +9117,6 @@ getTSTemplates(Archive *fout, int *numTSTemplates) /* Decide whether we want to dump it */ selectDumpableObject(&(tmplinfo[i].dobj), fout); - - /* Text Search Templates do not currently have ACLs. */ - tmplinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -9194,9 +9190,6 @@ getTSConfigurations(Archive *fout, int *numTSConfigs) /* Decide whether we want to dump it */ selectDumpableObject(&(cfginfo[i].dobj), fout); - - /* Text Search Configurations do not currently have ACLs. */ - cfginfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; } PQclear(res); @@ -9358,11 +9351,12 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) /* Decide whether we want to dump it */ selectDumpableObject(&(fdwinfo[i].dobj), fout); - /* Do not try to dump ACL if no ACL exists. */ - if (PQgetisnull(res, i, i_fdwacl) && PQgetisnull(res, i, i_rfdwacl) && - PQgetisnull(res, i, i_initfdwacl) && - PQgetisnull(res, i, i_initrfdwacl)) - fdwinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* 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))) + fdwinfo[i].dobj.components |= DUMP_COMPONENT_ACL; } PQclear(res); @@ -9508,11 +9502,15 @@ getForeignServers(Archive *fout, int *numForeignServers) /* Decide whether we want to dump it */ selectDumpableObject(&(srvinfo[i].dobj), fout); - /* Do not try to dump ACL if no ACL exists. */ - if (PQgetisnull(res, i, i_srvacl) && PQgetisnull(res, i, i_rsrvacl) && - PQgetisnull(res, i, i_initsrvacl) && - PQgetisnull(res, i, i_initrsrvacl)) - srvinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + /* Servers have user mappings */ + 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))) + srvinfo[i].dobj.components |= DUMP_COMPONENT_ACL; } PQclear(res); @@ -9664,6 +9662,9 @@ getDefaultACLs(Archive *fout, int *numDefaultACLs) 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; + /* Decide whether we want to dump it */ selectDumpableDefaultACL(&(daclinfo[i]), dopt); } @@ -9917,19 +9918,11 @@ static int findComments(Archive *fout, Oid classoid, Oid objoid, CommentItem **items) { - /* static storage for table of comments */ - static CommentItem *comments = NULL; - static int ncomments = -1; - CommentItem *middle = NULL; CommentItem *low; CommentItem *high; int nmatch; - /* Get comments if we didn't already */ - if (ncomments < 0) - ncomments = collectComments(fout, &comments); - /* * Do binary search to find some item matching the object. */ @@ -9990,15 +9983,17 @@ findComments(Archive *fout, Oid classoid, Oid objoid, /* * collectComments -- * - * Construct a table of all comments available for database objects. + * Construct a table of all comments available for database objects; + * also set the has-comment component flag for each relevant object. + * * We used to do per-object queries for the comments, but it's much faster * to pull them all over at once, and on most databases the memory cost * isn't high. * * The table is sorted by classoid/objid/objsubid for speed in lookup. */ -static int -collectComments(Archive *fout, CommentItem **items) +static void +collectComments(Archive *fout) { PGresult *res; PQExpBuffer query; @@ -10008,7 +10003,7 @@ collectComments(Archive *fout, CommentItem **items) int i_objsubid; int ntups; int i; - CommentItem *comments; + DumpableObject *dobj; query = createPQExpBuffer(); @@ -10028,20 +10023,52 @@ collectComments(Archive *fout, CommentItem **items) ntups = PQntuples(res); comments = (CommentItem *) pg_malloc(ntups * sizeof(CommentItem)); + ncomments = 0; + dobj = NULL; for (i = 0; i < ntups; i++) { - comments[i].descr = PQgetvalue(res, i, i_description); - comments[i].classoid = atooid(PQgetvalue(res, i, i_classoid)); - comments[i].objoid = atooid(PQgetvalue(res, i, i_objoid)); - comments[i].objsubid = atoi(PQgetvalue(res, i, i_objsubid)); + CatalogId objId; + int subid; + + objId.tableoid = atooid(PQgetvalue(res, i, i_classoid)); + objId.oid = atooid(PQgetvalue(res, i, i_objoid)); + subid = atoi(PQgetvalue(res, i, i_objsubid)); + + /* We needn't remember comments that don't match any dumpable object */ + if (dobj == NULL || + dobj->catId.tableoid != objId.tableoid || + dobj->catId.oid != objId.oid) + dobj = findObjectByCatalogId(objId); + if (dobj == NULL) + continue; + + /* + * Comments on columns of composite types are linked to the type's + * pg_class entry, but we need to set the DUMP_COMPONENT_COMMENT flag + * in the type's own DumpableObject. + */ + if (subid != 0 && dobj->objType == DO_TABLE && + ((TableInfo *) dobj)->relkind == RELKIND_COMPOSITE_TYPE) + { + TypeInfo *cTypeInfo; + + cTypeInfo = findTypeByOid(((TableInfo *) dobj)->reltype); + if (cTypeInfo) + cTypeInfo->dobj.components |= DUMP_COMPONENT_COMMENT; + } + else + dobj->components |= DUMP_COMPONENT_COMMENT; + + comments[ncomments].descr = pg_strdup(PQgetvalue(res, i, i_description)); + comments[ncomments].classoid = objId.tableoid; + comments[ncomments].objoid = objId.oid; + comments[ncomments].objsubid = subid; + ncomments++; } - /* Do NOT free the PGresult since we are keeping pointers into it */ + PQclear(res); destroyPQExpBuffer(query); - - *items = comments; - return ntups; } /* @@ -10051,8 +10078,19 @@ collectComments(Archive *fout, CommentItem **items) * ArchiveEntries (TOC objects) for each object to be dumped. */ static void -dumpDumpableObject(Archive *fout, const DumpableObject *dobj) +dumpDumpableObject(Archive *fout, DumpableObject *dobj) { + /* + * Clear any dump-request bits for components that don't exist for this + * object. (This makes it safe to initially use DUMP_COMPONENT_ALL as the + * request for every kind of object.) + */ + dobj->dump &= dobj->components; + + /* Now, short-circuit if there's nothing to be done here. */ + if (dobj->dump == 0) + return; + switch (dobj->objType) { case DO_NAMESPACE: @@ -10227,8 +10265,8 @@ dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo) PQExpBuffer delq; char *qnspname; - /* Skip if not to be dumped */ - if (!nspinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -10307,8 +10345,8 @@ dumpExtension(Archive *fout, const ExtensionInfo *extinfo) PQExpBuffer delq; char *qextname; - /* Skip if not to be dumped */ - if (!extinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -10432,8 +10470,8 @@ dumpType(Archive *fout, const TypeInfo *tyinfo) { DumpOptions *dopt = fout->dopt; - /* Skip if not to be dumped */ - if (!tyinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* Dump out in proper style */ @@ -11571,8 +11609,8 @@ dumpShellType(Archive *fout, const ShellTypeInfo *stinfo) DumpOptions *dopt = fout->dopt; PQExpBuffer q; - /* Skip if not to be dumped */ - if (!stinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -11623,8 +11661,8 @@ dumpProcLang(Archive *fout, const ProcLangInfo *plang) FuncInfo *inlineInfo = NULL; FuncInfo *validatorInfo = NULL; - /* Skip if not to be dumped */ - if (!plang->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* @@ -11911,8 +11949,8 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) const char *keyword; int i; - /* Skip if not to be dumped */ - if (!finfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -12405,8 +12443,8 @@ dumpCast(Archive *fout, const CastInfo *cast) const char *sourceType; const char *targetType; - /* Skip if not to be dumped */ - if (!cast->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* Cannot dump if we don't have the cast function's info */ @@ -12511,8 +12549,8 @@ dumpTransform(Archive *fout, const TransformInfo *transform) char *lanname; const char *transformType; - /* Skip if not to be dumped */ - if (!transform->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* Cannot dump if we don't have the transform functions' info */ @@ -12660,8 +12698,8 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo) char *oprregproc; char *oprref; - /* Skip if not to be dumped */ - if (!oprinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* @@ -12953,8 +12991,8 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo) PQExpBuffer delq; char *qamname; - /* Skip if not to be dumped */ - if (!aminfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -13058,8 +13096,8 @@ dumpOpclass(Archive *fout, const OpclassInfo *opcinfo) bool needComma; int i; - /* Skip if not to be dumped */ - if (!opcinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -13420,8 +13458,8 @@ dumpOpfamily(Archive *fout, const OpfamilyInfo *opfinfo) bool needComma; int i; - /* Skip if not to be dumped */ - if (!opfinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -13665,8 +13703,8 @@ dumpCollation(Archive *fout, const CollInfo *collinfo) const char *collcollate; const char *collctype; - /* Skip if not to be dumped */ - if (!collinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -13817,8 +13855,8 @@ dumpConversion(Archive *fout, const ConvInfo *convinfo) const char *conproc; bool condefault; - /* Skip if not to be dumped */ - if (!convinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -13965,8 +14003,8 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) const char *proparallel; char defaultfinalmodify; - /* Skip if not to be dumped */ - if (!agginfo->aggfn.dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -14299,8 +14337,8 @@ dumpTSParser(Archive *fout, const TSParserInfo *prsinfo) PQExpBuffer delq; char *qprsname; - /* Skip if not to be dumped */ - if (!prsinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -14367,8 +14405,8 @@ dumpTSDictionary(Archive *fout, const TSDictInfo *dictinfo) char *nspname; char *tmplname; - /* Skip if not to be dumped */ - if (!dictinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -14443,8 +14481,8 @@ dumpTSTemplate(Archive *fout, const TSTemplateInfo *tmplinfo) PQExpBuffer delq; char *qtmplname; - /* Skip if not to be dumped */ - if (!tmplinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -14509,8 +14547,8 @@ dumpTSConfig(Archive *fout, const TSConfigInfo *cfginfo) int i_tokenname; int i_dictname; - /* Skip if not to be dumped */ - if (!cfginfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -14621,8 +14659,8 @@ dumpForeignDataWrapper(Archive *fout, const FdwInfo *fdwinfo) PQExpBuffer delq; char *qfdwname; - /* Skip if not to be dumped */ - if (!fdwinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -14696,8 +14734,8 @@ dumpForeignServer(Archive *fout, const ForeignServerInfo *srvinfo) char *qsrvname; char *fdwname; - /* Skip if not to be dumped */ - if (!srvinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -14889,8 +14927,8 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo) PQExpBuffer tag; const char *type; - /* Skip if not to be dumped */ - if (!daclinfo->dobj.dump || dopt->dataOnly || dopt->aclsSkip) + /* Do nothing in data-only dump, or if we're skipping ACLs */ + if (dopt->dataOnly || dopt->aclsSkip) return; q = createPQExpBuffer(); @@ -15245,20 +15283,12 @@ dumpTableSecLabel(Archive *fout, const TableInfo *tbinfo, const char *reltypenam static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, SecLabelItem **items) { - /* static storage for table of security labels */ - static SecLabelItem *labels = NULL; - static int nlabels = -1; - SecLabelItem *middle = NULL; SecLabelItem *low; SecLabelItem *high; int nmatch; - /* Get security labels if we didn't already */ - if (nlabels < 0) - nlabels = collectSecLabels(fout, &labels); - - if (nlabels <= 0) /* no labels, so no match is possible */ + if (nseclabels <= 0) /* no labels, so no match is possible */ { *items = NULL; return 0; @@ -15267,8 +15297,8 @@ findSecLabels(Archive *fout, Oid classoid, Oid objoid, SecLabelItem **items) /* * Do binary search to find some item matching the object. */ - low = &labels[0]; - high = &labels[nlabels - 1]; + low = &seclabels[0]; + high = &seclabels[nseclabels - 1]; while (low <= high) { middle = low + (high - low) / 2; @@ -15324,13 +15354,13 @@ findSecLabels(Archive *fout, Oid classoid, Oid objoid, SecLabelItem **items) /* * collectSecLabels * - * Construct a table of all security labels available for database objects. - * It's much faster to pull them all at once. + * Construct a table of all security labels available for database objects; + * also set the has-seclabel component flag for each relevant object. * * The table is sorted by classoid/objid/objsubid for speed in lookup. */ -static int -collectSecLabels(Archive *fout, SecLabelItem **items) +static void +collectSecLabels(Archive *fout) { PGresult *res; PQExpBuffer query; @@ -15341,7 +15371,7 @@ collectSecLabels(Archive *fout, SecLabelItem **items) int i_objsubid; int ntups; int i; - SecLabelItem *labels; + DumpableObject *dobj; query = createPQExpBuffer(); @@ -15361,22 +15391,54 @@ collectSecLabels(Archive *fout, SecLabelItem **items) ntups = PQntuples(res); - labels = (SecLabelItem *) pg_malloc(ntups * sizeof(SecLabelItem)); + seclabels = (SecLabelItem *) pg_malloc(ntups * sizeof(SecLabelItem)); + nseclabels = 0; + dobj = NULL; for (i = 0; i < ntups; i++) { - labels[i].label = PQgetvalue(res, i, i_label); - labels[i].provider = PQgetvalue(res, i, i_provider); - labels[i].classoid = atooid(PQgetvalue(res, i, i_classoid)); - labels[i].objoid = atooid(PQgetvalue(res, i, i_objoid)); - labels[i].objsubid = atoi(PQgetvalue(res, i, i_objsubid)); + CatalogId objId; + int subid; + + objId.tableoid = atooid(PQgetvalue(res, i, i_classoid)); + objId.oid = atooid(PQgetvalue(res, i, i_objoid)); + subid = atoi(PQgetvalue(res, i, i_objsubid)); + + /* We needn't remember labels that don't match any dumpable object */ + if (dobj == NULL || + dobj->catId.tableoid != objId.tableoid || + dobj->catId.oid != objId.oid) + dobj = findObjectByCatalogId(objId); + if (dobj == NULL) + continue; + + /* + * Labels on columns of composite types are linked to the type's + * pg_class entry, but we need to set the DUMP_COMPONENT_SECLABEL flag + * in the type's own DumpableObject. + */ + if (subid != 0 && dobj->objType == DO_TABLE && + ((TableInfo *) dobj)->relkind == RELKIND_COMPOSITE_TYPE) + { + TypeInfo *cTypeInfo; + + cTypeInfo = findTypeByOid(((TableInfo *) dobj)->reltype); + if (cTypeInfo) + cTypeInfo->dobj.components |= DUMP_COMPONENT_SECLABEL; + } + else + dobj->components |= DUMP_COMPONENT_SECLABEL; + + seclabels[nseclabels].label = pg_strdup(PQgetvalue(res, i, i_label)); + seclabels[nseclabels].provider = pg_strdup(PQgetvalue(res, i, i_provider)); + seclabels[nseclabels].classoid = objId.tableoid; + seclabels[nseclabels].objoid = objId.oid; + seclabels[nseclabels].objsubid = subid; + nseclabels++; } - /* Do NOT free the PGresult since we are keeping pointers into it */ + PQclear(res); destroyPQExpBuffer(query); - - *items = labels; - return ntups; } /* @@ -15390,17 +15452,17 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) DumpId tableAclDumpId = InvalidDumpId; char *namecopy; - /* - * noop if we are not dumping anything about this table, or if we are - * doing a data-only dump - */ - if (!tbinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; - if (tbinfo->relkind == RELKIND_SEQUENCE) - dumpSequence(fout, tbinfo); - else - dumpTableSchema(fout, tbinfo); + if (tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + { + if (tbinfo->relkind == RELKIND_SEQUENCE) + dumpSequence(fout, tbinfo); + else + dumpTableSchema(fout, tbinfo); + } /* Handle the ACL here */ namecopy = pg_strdup(fmtId(tbinfo->dobj.name)); @@ -15420,7 +15482,8 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) /* * Handle column ACLs, if any. Note: we pull these with a separate query * rather than trying to fetch them during getTableAttrs, so that we won't - * miss ACLs on system columns. + * 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) { @@ -15639,8 +15702,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) qrelname); if (dopt->binary_upgrade) - binary_upgrade_set_type_oids_by_rel_oid(fout, q, - tbinfo->dobj.catId.oid); + binary_upgrade_set_type_oids_by_rel(fout, q, tbinfo); /* Is it a table or a view? */ if (tbinfo->relkind == RELKIND_VIEW) @@ -16381,6 +16443,7 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) DumpOptions *dopt = fout->dopt; PQExpBuffer q; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -16431,8 +16494,8 @@ dumpAttrDef(Archive *fout, const AttrDefInfo *adinfo) char *tag; char *foreign; - /* Skip if table definition not to be dumped */ - if (!tbinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* Skip if not "separate"; it was dumped in the table's definition */ @@ -16520,6 +16583,7 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) char *qindxname; char *qqindxname; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -16654,6 +16718,7 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) static void dumpIndexAttach(Archive *fout, const IndexAttachInfo *attachinfo) { + /* Do nothing in data-only dump */ if (fout->dopt->dataOnly) return; @@ -16700,8 +16765,8 @@ dumpStatisticsExt(Archive *fout, const StatsExtInfo *statsextinfo) PGresult *res; char *stxdef; - /* Skip if not to be dumped */ - if (!statsextinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -16777,8 +16842,8 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) char *tag = NULL; char *foreign; - /* Skip if not to be dumped */ - if (!coninfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; q = createPQExpBuffer(); @@ -17430,10 +17495,7 @@ dumpTrigger(Archive *fout, const TriggerInfo *tginfo) int findx; char *tag; - /* - * we needn't check dobj.dump because TriggerInfo wouldn't have been - * created in the first place for non-dumpable triggers - */ + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -17669,8 +17731,8 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo) PQExpBuffer delqry; char *qevtname; - /* Skip if not to be dumped */ - if (!evtinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; query = createPQExpBuffer(); @@ -17760,8 +17822,8 @@ dumpRule(Archive *fout, const RuleInfo *rinfo) PGresult *res; char *tag; - /* Skip if not to be dumped */ - if (!rinfo->dobj.dump || dopt->dataOnly) + /* Do nothing in data-only dump */ + if (dopt->dataOnly) return; /* diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index cc55e598ec..3db73f710c 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -84,8 +84,13 @@ typedef enum DO_SUBSCRIPTION } DumpableObjectType; -/* component types of an object which can be selected for dumping */ -typedef uint32 DumpComponents; /* a bitmask of dump object components */ +/* + * DumpComponents is a bitmask of the potentially dumpable components of + * a database object: its core definition, plus optional attributes such + * as ACL, comments, etc. The NONE and ALL symbols are convenient + * shorthands. + */ +typedef uint32 DumpComponents; #define DUMP_COMPONENT_NONE (0) #define DUMP_COMPONENT_DEFINITION (1 << 0) #define DUMP_COMPONENT_DATA (1 << 1) @@ -130,8 +135,9 @@ typedef struct _dumpableObject DumpId dumpId; /* assigned by AssignDumpId() */ char *name; /* object name (should never be NULL) */ struct _namespaceInfo *namespace; /* containing namespace, or NULL */ - DumpComponents dump; /* bitmask of components to dump */ + DumpComponents dump; /* bitmask of components requested to dump */ DumpComponents dump_contains; /* as above, but for contained objects */ + DumpComponents components; /* bitmask of components available to dump */ bool ext_member; /* true if object is member of extension */ bool depends_on_ext; /* true if object depends on an extension */ DumpId *dependencies; /* dumpIds of objects this one depends on */ @@ -288,6 +294,7 @@ typedef struct _tableInfo uint32 toast_frozenxid; /* toast table's relfrozenxid, if any */ uint32 toast_minmxid; /* toast table's relminmxid */ int ncheck; /* # of CHECK expressions */ + Oid reltype; /* OID of table's composite type, if any */ char *reloftype; /* underlying type for typed table */ Oid foreign_server; /* foreign server oid, if applicable */ /* these two are set only if table is a sequence owned by a column: */ diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index ea67e52a3f..6e216313c6 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 f5465f19ae..fac7a05c91 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 e5ea2c082d..607abb97d3 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) @@ -2822,19 +2824,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; @@ -2846,40 +2847,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 " @@ -2888,13 +2863,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 " @@ -2908,7 +2884,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 " @@ -2922,7 +2899,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 " @@ -2936,8 +2914,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()", @@ -2956,7 +2934,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"); @@ -2970,8 +2948,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); @@ -3109,9 +3087,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 @@ -3433,59 +3414,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); @@ -3493,9 +3445,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); @@ -3512,20 +3462,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; /* @@ -3601,8 +3548,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); @@ -4837,7 +4783,6 @@ binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, NamespaceInfo * getNamespaces(Archive *fout, int *numNamespaces) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -4849,9 +4794,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(); @@ -4859,67 +4802,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); @@ -4935,9 +4829,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++) { @@ -4946,23 +4838,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); @@ -5085,7 +5015,6 @@ getExtensions(Archive *fout, int *numExtensions) TypeInfo * getTypes(Archive *fout, int *numTypes) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -5097,9 +5026,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; @@ -5123,52 +5050,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\" " @@ -5182,8 +5068,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\" " @@ -5197,8 +5082,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\" " @@ -5220,9 +5104,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"); @@ -5240,12 +5122,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); @@ -5271,10 +5153,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; /* @@ -5801,9 +5680,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 @@ -5811,16 +5688,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"); @@ -5829,10 +5698,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 " @@ -5844,10 +5711,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, @@ -5857,11 +5720,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) { @@ -5870,8 +5751,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 != " @@ -5895,8 +5775,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 != " @@ -5919,9 +5798,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++) { @@ -5932,16 +5809,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; @@ -5957,10 +5834,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; } @@ -5997,9 +5871,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: @@ -6021,30 +5893,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 " @@ -6067,10 +5929,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, @@ -6085,23 +5943,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, @@ -6154,9 +6012,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++) { @@ -6167,13 +6023,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; @@ -6188,10 +6044,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) @@ -6256,10 +6109,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; @@ -6400,67 +6250,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, @@ -6482,22 +6279,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"); /* @@ -6509,7 +6300,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 @@ -6583,10 +6376,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"); @@ -6615,6 +6405,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)); @@ -6661,10 +6455,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)); @@ -6700,23 +6490,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 @@ -7999,7 +7776,6 @@ getEventTriggers(Archive *fout, int *numEventTriggers) ProcLangInfo * getProcLangs(Archive *fout, int *numProcLangs) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -8013,56 +7789,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 " @@ -8075,8 +7825,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 " @@ -8089,8 +7838,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 " @@ -8103,8 +7851,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 " @@ -8128,9 +7875,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++) @@ -8141,24 +7886,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; } @@ -9209,7 +8951,6 @@ getTSConfigurations(Archive *fout, int *numTSConfigs) FdwInfo * getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -9222,9 +8963,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 */ @@ -9236,46 +8975,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) { @@ -9283,8 +8998,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) " @@ -9300,8 +9014,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) " @@ -9326,9 +9039,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++) @@ -9339,23 +9050,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; } @@ -9376,7 +9084,6 @@ getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers) ForeignServerInfo * getForeignServers(Archive *fout, int *numForeignServers) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -9390,9 +9097,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 */ @@ -9404,53 +9109,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) " @@ -9476,9 +9155,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++) @@ -9489,15 +9166,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); @@ -9506,10 +9183,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; } @@ -9540,9 +9214,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; @@ -9554,13 +9226,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 @@ -9568,59 +9243,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); @@ -9635,9 +9275,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++) { @@ -9655,12 +9293,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; @@ -9676,6 +9314,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 -- * @@ -10324,8 +10082,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); @@ -10616,8 +10373,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); @@ -10756,8 +10512,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); @@ -10828,8 +10583,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); @@ -11088,8 +10842,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); @@ -11245,8 +10998,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++) @@ -11467,8 +11219,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); @@ -11766,8 +11517,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); @@ -12403,8 +12153,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); @@ -14308,9 +14057,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) @@ -14709,9 +14456,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); @@ -14798,9 +14543,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) @@ -14964,15 +14707,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, @@ -15002,20 +14743,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. @@ -15025,11 +14753,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 */ @@ -15043,29 +14775,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) { @@ -15475,8 +15230,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); } /* @@ -15485,7 +15239,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; @@ -15493,55 +15247,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 " @@ -15555,11 +15291,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)); /* @@ -15570,7 +15311,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 3db73f710c..e3b864f6ba 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -145,16 +145,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 @@ -170,6 +190,7 @@ typedef struct _extensionInfo typedef struct _typeInfo { DumpableObject dobj; + DumpableAcl dacl; /* * Note: dobj.name is the raw pg_type.typname entry. ftypname is the @@ -178,10 +199,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 */ @@ -206,15 +223,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 */ @@ -269,11 +283,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 */ @@ -285,6 +296,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? */ @@ -477,14 +489,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; @@ -549,49 +558,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 c29101704a..44114f3f71 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 3efee4e7ee..81e623602e 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 caafb97d29..e12e61cddb 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); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 607abb97d3..d6075133a1 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6110,20 +6110,23 @@ getTables(Archive *fout, int *numTables) int i_is_identity_sequence; int i_relacl; int i_acldefault; - int i_partkeydef; int i_ispartition; - int i_partbound; /* * Find all the tables and table-like objects. * + * We must fetch all tables in this phase because otherwise we cannot + * correctly identify inherited columns, owned sequences, etc. + * * We include system catalogs, so that we can work if a user table is * defined to inherit from a system catalog (pretty weird, but...) * * Note: in this phase we should collect only a minimal amount of * information about each table, basically just enough to decide if it is - * interesting. We must fetch all tables in this phase because otherwise - * we cannot correctly identify inherited columns, owned sequences, etc. + * interesting. In particular, since we do not yet have lock on any user + * table, we MUST NOT invoke any server-side data collection functions + * (for instance, pg_get_partkeydef()). Those are likely to fail or give + * wrong answers if any concurrent DDL is happening. */ appendPQExpBuffer(query, @@ -6217,10 +6220,10 @@ getTables(Archive *fout, int *numTables) if (fout->remoteVersion >= 90000) appendPQExpBufferStr(query, - "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype,"); + "c.reloftype, "); else appendPQExpBufferStr(query, - "NULL AS reloftype, "); + "0 AS reloftype, "); if (fout->remoteVersion >= 90100) appendPQExpBufferStr(query, @@ -6261,14 +6264,10 @@ getTables(Archive *fout, int *numTables) if (fout->remoteVersion >= 100000) appendPQExpBufferStr(query, - "pg_get_partkeydef(c.oid) AS partkeydef, " - "c.relispartition AS ispartition, " - "pg_get_expr(c.relpartbound, c.oid) AS partbound "); + "c.relispartition AS ispartition "); else appendPQExpBufferStr(query, - "NULL AS partkeydef, " - "false AS ispartition, " - "NULL AS partbound "); + "false AS ispartition "); /* * Left join to pg_depend to pick up dependency info linking sequences to @@ -6377,9 +6376,7 @@ getTables(Archive *fout, int *numTables) i_is_identity_sequence = PQfnumber(res, "is_identity_sequence"); i_relacl = PQfnumber(res, "relacl"); i_acldefault = PQfnumber(res, "acldefault"); - i_partkeydef = PQfnumber(res, "partkeydef"); i_ispartition = PQfnumber(res, "ispartition"); - i_partbound = PQfnumber(res, "partbound"); if (dopt->lockWaitTimeout) { @@ -6445,19 +6442,14 @@ getTables(Archive *fout, int *numTables) else tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption)); tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions)); - if (PQgetisnull(res, i, i_reloftype)) - tblinfo[i].reloftype = NULL; - else - tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype)); + tblinfo[i].reloftype = atooid(PQgetvalue(res, i, i_reloftype)); tblinfo[i].foreign_server = atooid(PQgetvalue(res, i, i_foreignserver)); if (PQgetisnull(res, i, i_amname)) tblinfo[i].amname = NULL; 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].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)); /* other fields were zeroed above */ @@ -15485,12 +15477,34 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } else { + char *partkeydef = NULL; char *ftoptions = NULL; char *srvname = NULL; char *foreign = ""; + /* + * Set reltypename, and collect any relkind-specific data that we + * didn't fetch during getTables(). + */ switch (tbinfo->relkind) { + case RELKIND_PARTITIONED_TABLE: + { + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + + reltypename = "TABLE"; + + /* retrieve partition key definition */ + appendPQExpBuffer(query, + "SELECT pg_get_partkeydef('%u')", + tbinfo->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); + partkeydef = pg_strdup(PQgetvalue(res, 0, 0)); + PQclear(res); + destroyPQExpBuffer(query); + break; + } case RELKIND_FOREIGN_TABLE: { PQExpBuffer query = createPQExpBuffer(); @@ -15530,6 +15544,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) break; default: reltypename = "TABLE"; + break; } numParents = tbinfo->numParents; @@ -15551,8 +15566,10 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) * Attach to type, if reloftype; except in case of a binary upgrade, * we dump the table normally and attach it to the type afterward. */ - if (tbinfo->reloftype && !dopt->binary_upgrade) - appendPQExpBuffer(q, " OF %s", tbinfo->reloftype); + if (OidIsValid(tbinfo->reloftype) && !dopt->binary_upgrade) + appendPQExpBuffer(q, " OF %s", + getFormattedTypeName(fout, tbinfo->reloftype, + zeroIsError)); if (tbinfo->relkind != RELKIND_MATVIEW) { @@ -15590,7 +15607,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) * Skip column if fully defined by reloftype, except in * binary upgrade */ - if (tbinfo->reloftype && !print_default && !print_notnull && + if (OidIsValid(tbinfo->reloftype) && + !print_default && !print_notnull && !dopt->binary_upgrade) continue; @@ -15623,7 +15641,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) * table ('OF type_name'), but in binary-upgrade mode, * print it in that case too. */ - if (dopt->binary_upgrade || !tbinfo->reloftype) + if (dopt->binary_upgrade || !OidIsValid(tbinfo->reloftype)) { appendPQExpBuffer(q, " %s", tbinfo->atttypnames[j]); @@ -15686,7 +15704,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) if (actual_atts) appendPQExpBufferStr(q, "\n)"); - else if (!(tbinfo->reloftype && !dopt->binary_upgrade)) + else if (!(OidIsValid(tbinfo->reloftype) && !dopt->binary_upgrade)) { /* * No attributes? we must have a parenthesized attribute list, @@ -15715,7 +15733,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE) - appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef); + appendPQExpBuffer(q, "\nPARTITION BY %s", partkeydef); if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname)); @@ -15898,12 +15916,13 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } } - if (tbinfo->reloftype) + if (OidIsValid(tbinfo->reloftype)) { appendPQExpBufferStr(q, "\n-- For binary upgrade, set up typed tables this way.\n"); appendPQExpBuffer(q, "ALTER TABLE ONLY %s OF %s;\n", qualrelname, - tbinfo->reloftype); + getFormattedTypeName(fout, tbinfo->reloftype, + zeroIsError)); } } @@ -16076,6 +16095,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) tbinfo->attfdwoptions[j]); } /* end loop over columns */ + if (partkeydef) + free(partkeydef); if (ftoptions) free(ftoptions); if (srvname) @@ -16183,6 +16204,8 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) { DumpOptions *dopt = fout->dopt; PQExpBuffer q; + PGresult *res; + char *partbound; /* Do nothing in data-only dump */ if (dopt->dataOnly) @@ -16193,14 +16216,23 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) q = createPQExpBuffer(); - /* Perform ALTER TABLE on the parent */ + /* Fetch the partition's partbound */ appendPQExpBuffer(q, + "SELECT pg_get_expr(c.relpartbound, c.oid) " + "FROM pg_class c " + "WHERE c.oid = '%u'", + attachinfo->partitionTbl->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, q->data); + partbound = PQgetvalue(res, 0, 0); + + /* Perform ALTER TABLE on the parent */ + printfPQExpBuffer(q, "ALTER TABLE ONLY %s ", fmtQualifiedDumpable(attachinfo->parentTbl)); appendPQExpBuffer(q, "ATTACH PARTITION %s %s;\n", fmtQualifiedDumpable(attachinfo->partitionTbl), - attachinfo->partitionTbl->partbound); + partbound); /* * There is no point in creating a drop query as the drop is done by table @@ -16217,6 +16249,7 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) .section = SECTION_PRE_DATA, .createStmt = q->data)); + PQclear(res); destroyPQExpBuffer(q); } diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index e3b864f6ba..9061812c08 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -307,7 +307,7 @@ typedef struct _tableInfo uint32 toast_minmxid; /* toast table's relminmxid */ int ncheck; /* # of CHECK expressions */ Oid reltype; /* OID of table's composite type, if any */ - char *reloftype; /* underlying type for typed table */ + Oid reloftype; /* underlying type for typed table */ Oid foreign_server; /* foreign server oid, if applicable */ /* these two are set only if table is a sequence owned by a column: */ Oid owning_tab; /* OID of table owning sequence */ @@ -346,8 +346,6 @@ typedef struct _tableInfo bool *inhNotNull; /* true if NOT NULL is inherited */ struct _attrDefInfo **attrdefs; /* DEFAULT expressions */ struct _constraintInfo *checkexprs; /* CHECK constraints */ - char *partkeydef; /* partition key definition */ - char *partbound; /* partition bound definition */ bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */ char *amname; /* relation access method */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d6075133a1..45bae2ffe1 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6650,13 +6650,15 @@ getInherits(Archive *fout, int *numInherits) void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) { - int i, - j; PQExpBuffer query = createPQExpBuffer(); + PQExpBuffer tbloids = createPQExpBuffer(); PGresult *res; + int ntups; + int curtblindx; IndxInfo *indxinfo; int i_tableoid, i_oid, + i_indrelid, i_indexname, i_parentidx, i_indexdef, @@ -6676,9 +6678,17 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_indreloptions, i_indstatcols, i_indstatvals; - int ntups; - for (i = 0; i < numTables; i++) + /* + * We want to perform just one query against pg_index. However, we + * mustn't try to select every row of the catalog and then sort it out on + * the client side, because some of the server-side functions we need + * would be unsafe to apply to tables we don't have lock on. Hence, we + * build an array of the OIDs of tables we care about (and now have lock + * on!), and use a WHERE clause to constrain which rows are selected. + */ + appendPQExpBufferChar(tbloids, '{'); + for (int i = 0; i < numTables; i++) { TableInfo *tbinfo = &tblinfo[i]; @@ -6691,232 +6701,270 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) if (!tbinfo->interesting) continue; - pg_log_info("reading indexes for table \"%s.%s\"", - tbinfo->dobj.namespace->dobj.name, - tbinfo->dobj.name); + /* OK, we need info for this table */ + if (tbloids->len > 1) + appendPQExpBufferChar(tbloids, ','); + appendPQExpBuffer(tbloids, "%u", tbinfo->dobj.catId.oid); + } + appendPQExpBufferChar(tbloids, '}'); + /* + * The point of the messy-looking outer join is to find a constraint that + * is related by an internal dependency link to the index. If we find one, + * create a CONSTRAINT entry linked to the INDEX entry. We assume an + * index won't have more than one internal dependency. + * + * As of 9.0 we don't need to look at pg_depend but can check for a match + * to pg_constraint.conindid. The check on conrelid is redundant but + * useful because that column is indexed while conindid is not. + */ + if (fout->remoteVersion >= 110000) + { + appendPQExpBuffer(query, + "SELECT t.tableoid, t.oid, i.indrelid, " + "t.relname AS indexname, " + "inh.inhparent AS parentidx, " + "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " + "i.indnkeyatts AS indnkeyatts, " + "i.indnatts AS indnatts, " + "i.indkey, i.indisclustered, " + "i.indisreplident, " + "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " + "c.tableoid AS contableoid, " + "c.oid AS conoid, " + "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " + "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," + "t.reloptions AS indreloptions, " + "(SELECT pg_catalog.array_agg(attnum ORDER BY attnum) " + " FROM pg_catalog.pg_attribute " + " WHERE attrelid = i.indexrelid AND " + " attstattarget >= 0) AS indstatcols," + "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) " + " FROM pg_catalog.pg_attribute " + " WHERE attrelid = i.indexrelid AND " + " attstattarget >= 0) AS indstatvals " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_index i ON (src.tbloid = i.indrelid) " + "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " + "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) " + "LEFT JOIN pg_catalog.pg_constraint c " + "ON (i.indrelid = c.conrelid AND " + "i.indexrelid = c.conindid AND " + "c.contype IN ('p','u','x')) " + "LEFT JOIN pg_catalog.pg_inherits inh " + "ON (inh.inhrelid = indexrelid) " + "WHERE (i.indisvalid OR t2.relkind = 'p') " + "AND i.indisready " + "ORDER BY i.indrelid, indexname", + tbloids->data); + } + else if (fout->remoteVersion >= 90400) + { /* - * The point of the messy-looking outer join is to find a constraint - * that is related by an internal dependency link to the index. If we - * find one, create a CONSTRAINT entry linked to the INDEX entry. We - * assume an index won't have more than one internal dependency. - * - * As of 9.0 we don't need to look at pg_depend but can check for a - * match to pg_constraint.conindid. The check on conrelid is - * redundant but useful because that column is indexed while conindid - * is not. + * the test on indisready is necessary in 9.2, and harmless in + * earlier/later versions */ - resetPQExpBuffer(query); - if (fout->remoteVersion >= 110000) - { - appendPQExpBuffer(query, - "SELECT t.tableoid, t.oid, " - "t.relname AS indexname, " - "inh.inhparent AS parentidx, " - "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " - "i.indnkeyatts AS indnkeyatts, " - "i.indnatts AS indnatts, " - "i.indkey, i.indisclustered, " - "i.indisreplident, " - "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " - "c.tableoid AS contableoid, " - "c.oid AS conoid, " - "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " - "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," - "t.reloptions AS indreloptions, " - "(SELECT pg_catalog.array_agg(attnum ORDER BY attnum) " - " FROM pg_catalog.pg_attribute " - " WHERE attrelid = i.indexrelid AND " - " attstattarget >= 0) AS indstatcols," - "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) " - " FROM pg_catalog.pg_attribute " - " WHERE attrelid = i.indexrelid AND " - " attstattarget >= 0) AS indstatvals " - "FROM pg_catalog.pg_index i " - "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " - "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) " - "LEFT JOIN pg_catalog.pg_constraint c " - "ON (i.indrelid = c.conrelid AND " - "i.indexrelid = c.conindid AND " - "c.contype IN ('p','u','x')) " - "LEFT JOIN pg_catalog.pg_inherits inh " - "ON (inh.inhrelid = indexrelid) " - "WHERE i.indrelid = '%u'::pg_catalog.oid " - "AND (i.indisvalid OR t2.relkind = 'p') " - "AND i.indisready " - "ORDER BY indexname", - tbinfo->dobj.catId.oid); - } - else if (fout->remoteVersion >= 90400) - { - /* - * the test on indisready is necessary in 9.2, and harmless in - * earlier/later versions - */ - appendPQExpBuffer(query, - "SELECT t.tableoid, t.oid, " - "t.relname AS indexname, " - "0 AS parentidx, " - "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " - "i.indnatts AS indnkeyatts, " - "i.indnatts AS indnatts, " - "i.indkey, i.indisclustered, " - "i.indisreplident, " - "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " - "c.tableoid AS contableoid, " - "c.oid AS conoid, " - "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " - "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," - "t.reloptions AS indreloptions, " - "'' AS indstatcols, " - "'' AS indstatvals " - "FROM pg_catalog.pg_index i " - "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " - "LEFT JOIN pg_catalog.pg_constraint c " - "ON (i.indrelid = c.conrelid AND " - "i.indexrelid = c.conindid AND " - "c.contype IN ('p','u','x')) " - "WHERE i.indrelid = '%u'::pg_catalog.oid " - "AND i.indisvalid AND i.indisready " - "ORDER BY indexname", - tbinfo->dobj.catId.oid); - } - else if (fout->remoteVersion >= 90000) - { - /* - * the test on indisready is necessary in 9.2, and harmless in - * earlier/later versions - */ - appendPQExpBuffer(query, - "SELECT t.tableoid, t.oid, " - "t.relname AS indexname, " - "0 AS parentidx, " - "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " - "i.indnatts AS indnkeyatts, " - "i.indnatts AS indnatts, " - "i.indkey, i.indisclustered, " - "false AS indisreplident, " - "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " - "c.tableoid AS contableoid, " - "c.oid AS conoid, " - "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " - "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," - "t.reloptions AS indreloptions, " - "'' AS indstatcols, " - "'' AS indstatvals " - "FROM pg_catalog.pg_index i " - "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " - "LEFT JOIN pg_catalog.pg_constraint c " - "ON (i.indrelid = c.conrelid AND " - "i.indexrelid = c.conindid AND " - "c.contype IN ('p','u','x')) " - "WHERE i.indrelid = '%u'::pg_catalog.oid " - "AND i.indisvalid AND i.indisready " - "ORDER BY indexname", - tbinfo->dobj.catId.oid); - } - else if (fout->remoteVersion >= 80200) - { - appendPQExpBuffer(query, - "SELECT t.tableoid, t.oid, " - "t.relname AS indexname, " - "0 AS parentidx, " - "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " - "i.indnatts AS indnkeyatts, " - "i.indnatts AS indnatts, " - "i.indkey, i.indisclustered, " - "false AS indisreplident, " - "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " - "c.tableoid AS contableoid, " - "c.oid AS conoid, " - "null AS condef, " - "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," - "t.reloptions AS indreloptions, " - "'' AS indstatcols, " - "'' AS indstatvals " - "FROM pg_catalog.pg_index i " - "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " - "LEFT JOIN pg_catalog.pg_depend d " - "ON (d.classid = t.tableoid " - "AND d.objid = t.oid " - "AND d.deptype = 'i') " - "LEFT JOIN pg_catalog.pg_constraint c " - "ON (d.refclassid = c.tableoid " - "AND d.refobjid = c.oid) " - "WHERE i.indrelid = '%u'::pg_catalog.oid " - "AND i.indisvalid " - "ORDER BY indexname", - tbinfo->dobj.catId.oid); - } - else - { - appendPQExpBuffer(query, - "SELECT t.tableoid, t.oid, " - "t.relname AS indexname, " - "0 AS parentidx, " - "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " - "t.relnatts AS indnkeyatts, " - "t.relnatts AS indnatts, " - "i.indkey, i.indisclustered, " - "false AS indisreplident, " - "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " - "c.tableoid AS contableoid, " - "c.oid AS conoid, " - "null AS condef, " - "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," - "null AS indreloptions, " - "'' AS indstatcols, " - "'' AS indstatvals " - "FROM pg_catalog.pg_index i " - "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " - "LEFT JOIN pg_catalog.pg_depend d " - "ON (d.classid = t.tableoid " - "AND d.objid = t.oid " - "AND d.deptype = 'i') " - "LEFT JOIN pg_catalog.pg_constraint c " - "ON (d.refclassid = c.tableoid " - "AND d.refobjid = c.oid) " - "WHERE i.indrelid = '%u'::pg_catalog.oid " - "ORDER BY indexname", - tbinfo->dobj.catId.oid); - } + appendPQExpBuffer(query, + "SELECT t.tableoid, t.oid, i.indrelid, " + "t.relname AS indexname, " + "0 AS parentidx, " + "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " + "i.indnatts AS indnkeyatts, " + "i.indnatts AS indnatts, " + "i.indkey, i.indisclustered, " + "i.indisreplident, " + "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " + "c.tableoid AS contableoid, " + "c.oid AS conoid, " + "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " + "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," + "t.reloptions AS indreloptions, " + "'' AS indstatcols, " + "'' AS indstatvals " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_index i ON (src.tbloid = i.indrelid) " + "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " + "LEFT JOIN pg_catalog.pg_constraint c " + "ON (i.indrelid = c.conrelid AND " + "i.indexrelid = c.conindid AND " + "c.contype IN ('p','u','x')) " + "WHERE i.indisvalid AND i.indisready " + "ORDER BY i.indrelid, indexname", + tbloids->data); + } + else if (fout->remoteVersion >= 90000) + { + /* + * the test on indisready is necessary in 9.2, and harmless in + * earlier/later versions + */ + appendPQExpBuffer(query, + "SELECT t.tableoid, t.oid, i.indrelid, " + "t.relname AS indexname, " + "0 AS parentidx, " + "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " + "i.indnatts AS indnkeyatts, " + "i.indnatts AS indnatts, " + "i.indkey, i.indisclustered, " + "false AS indisreplident, " + "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " + "c.tableoid AS contableoid, " + "c.oid AS conoid, " + "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " + "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," + "t.reloptions AS indreloptions, " + "'' AS indstatcols, " + "'' AS indstatvals " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_index i ON (src.tbloid = i.indrelid) " + "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " + "LEFT JOIN pg_catalog.pg_constraint c " + "ON (i.indrelid = c.conrelid AND " + "i.indexrelid = c.conindid AND " + "c.contype IN ('p','u','x')) " + "WHERE i.indisvalid AND i.indisready " + "ORDER BY i.indrelid, indexname", + tbloids->data); + } + else if (fout->remoteVersion >= 80200) + { + appendPQExpBuffer(query, + "SELECT t.tableoid, t.oid, i.indrelid, " + "t.relname AS indexname, " + "0 AS parentidx, " + "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " + "i.indnatts AS indnkeyatts, " + "i.indnatts AS indnatts, " + "i.indkey, i.indisclustered, " + "false AS indisreplident, " + "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " + "c.tableoid AS contableoid, " + "c.oid AS conoid, " + "null AS condef, " + "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," + "t.reloptions AS indreloptions, " + "'' AS indstatcols, " + "'' AS indstatvals " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_index i ON (src.tbloid = i.indrelid) " + "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " + "LEFT JOIN pg_catalog.pg_depend d " + "ON (d.classid = t.tableoid " + "AND d.objid = t.oid " + "AND d.deptype = 'i') " + "LEFT JOIN pg_catalog.pg_constraint c " + "ON (d.refclassid = c.tableoid " + "AND d.refobjid = c.oid) " + "WHERE i.indisvalid " + "ORDER BY i.indrelid, indexname", + tbloids->data); + } + else + { + appendPQExpBuffer(query, + "SELECT t.tableoid, t.oid, i.indrelid, " + "t.relname AS indexname, " + "0 AS parentidx, " + "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " + "t.relnatts AS indnkeyatts, " + "t.relnatts AS indnatts, " + "i.indkey, i.indisclustered, " + "false AS indisreplident, " + "c.contype, c.conname, " + "c.condeferrable, c.condeferred, " + "c.tableoid AS contableoid, " + "c.oid AS conoid, " + "null AS condef, " + "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace," + "null AS indreloptions, " + "'' AS indstatcols, " + "'' AS indstatvals " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_index i ON (src.tbloid = i.indrelid) " + "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) " + "LEFT JOIN pg_catalog.pg_depend d " + "ON (d.classid = t.tableoid " + "AND d.objid = t.oid " + "AND d.deptype = 'i') " + "LEFT JOIN pg_catalog.pg_constraint c " + "ON (d.refclassid = c.tableoid " + "AND d.refobjid = c.oid) " + "ORDER BY i.indrelid, indexname", + tbloids->data); + } - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - ntups = PQntuples(res); + ntups = PQntuples(res); - i_tableoid = PQfnumber(res, "tableoid"); - i_oid = PQfnumber(res, "oid"); - i_indexname = PQfnumber(res, "indexname"); - i_parentidx = PQfnumber(res, "parentidx"); - i_indexdef = PQfnumber(res, "indexdef"); - i_indnkeyatts = PQfnumber(res, "indnkeyatts"); - i_indnatts = PQfnumber(res, "indnatts"); - i_indkey = PQfnumber(res, "indkey"); - i_indisclustered = PQfnumber(res, "indisclustered"); - i_indisreplident = PQfnumber(res, "indisreplident"); - i_contype = PQfnumber(res, "contype"); - i_conname = PQfnumber(res, "conname"); - i_condeferrable = PQfnumber(res, "condeferrable"); - i_condeferred = PQfnumber(res, "condeferred"); - i_contableoid = PQfnumber(res, "contableoid"); - i_conoid = PQfnumber(res, "conoid"); - i_condef = PQfnumber(res, "condef"); - i_tablespace = PQfnumber(res, "tablespace"); - i_indreloptions = PQfnumber(res, "indreloptions"); - i_indstatcols = PQfnumber(res, "indstatcols"); - i_indstatvals = PQfnumber(res, "indstatvals"); - - tbinfo->indexes = indxinfo = - (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo)); - tbinfo->numIndexes = ntups; + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_indrelid = PQfnumber(res, "indrelid"); + i_indexname = PQfnumber(res, "indexname"); + i_parentidx = PQfnumber(res, "parentidx"); + i_indexdef = PQfnumber(res, "indexdef"); + i_indnkeyatts = PQfnumber(res, "indnkeyatts"); + i_indnatts = PQfnumber(res, "indnatts"); + i_indkey = PQfnumber(res, "indkey"); + i_indisclustered = PQfnumber(res, "indisclustered"); + i_indisreplident = PQfnumber(res, "indisreplident"); + i_contype = PQfnumber(res, "contype"); + i_conname = PQfnumber(res, "conname"); + i_condeferrable = PQfnumber(res, "condeferrable"); + i_condeferred = PQfnumber(res, "condeferred"); + i_contableoid = PQfnumber(res, "contableoid"); + i_conoid = PQfnumber(res, "conoid"); + i_condef = PQfnumber(res, "condef"); + i_tablespace = PQfnumber(res, "tablespace"); + i_indreloptions = PQfnumber(res, "indreloptions"); + i_indstatcols = PQfnumber(res, "indstatcols"); + i_indstatvals = PQfnumber(res, "indstatvals"); - for (j = 0; j < ntups; j++) + indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo)); + + /* + * Outer loop iterates once per table, not once per row. Incrementing of + * j is handled by the inner loop. + */ + curtblindx = -1; + for (int j = 0; j < ntups;) + { + Oid indrelid = atooid(PQgetvalue(res, j, i_indrelid)); + TableInfo *tbinfo = NULL; + int numinds; + + /* Count rows for this table */ + for (numinds = 1; numinds < ntups - j; numinds++) + if (atooid(PQgetvalue(res, j + numinds, i_indrelid)) != indrelid) + break; + + /* + * Locate the associated TableInfo; we rely on tblinfo[] being in OID + * order. + */ + while (++curtblindx < numTables) + { + tbinfo = &tblinfo[curtblindx]; + if (tbinfo->dobj.catId.oid == indrelid) + break; + } + if (curtblindx >= numTables) + fatal("unrecognized table OID %u", indrelid); + /* cross-check that we only got requested tables */ + if (!tbinfo->hasindex || + !tbinfo->interesting) + fatal("unexpected index data for table \"%s\"", + tbinfo->dobj.name); + + /* Save data for this table */ + tbinfo->indexes = indxinfo + j; + tbinfo->numIndexes = numinds; + + for (int c = 0; c < numinds; c++, j++) { char contype; @@ -6985,11 +7033,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) indxinfo[j].indexconstraint = 0; } } - - PQclear(res); } + PQclear(res); + destroyPQExpBuffer(query); + destroyPQExpBuffer(tbloids); } /* @@ -7076,22 +7125,31 @@ getExtendedStatistics(Archive *fout) void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables) { - int i, - j; - ConstraintInfo *constrinfo; - PQExpBuffer query; + PQExpBuffer query = createPQExpBuffer(); + PQExpBuffer tbloids = createPQExpBuffer(); PGresult *res; + int ntups; + int curtblindx; + TableInfo *tbinfo = NULL; + ConstraintInfo *constrinfo; int i_contableoid, i_conoid, + i_conrelid, i_conname, i_confrelid, i_conindid, i_condef; - int ntups; - query = createPQExpBuffer(); - - for (i = 0; i < numTables; i++) + /* + * We want to perform just one query against pg_constraint. However, we + * mustn't try to select every row of the catalog and then sort it out on + * the client side, because some of the server-side functions we need + * would be unsafe to apply to tables we don't have lock on. Hence, we + * build an array of the OIDs of tables we care about (and now have lock + * on!), and use a WHERE clause to constrain which rows are selected. + */ + appendPQExpBufferChar(tbloids, '{'); + for (int i = 0; i < numTables; i++) { TableInfo *tbinfo = &tblinfo[i]; @@ -7104,95 +7162,118 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables) !(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)) continue; - pg_log_info("reading foreign key constraints for table \"%s.%s\"", - tbinfo->dobj.namespace->dobj.name, - tbinfo->dobj.name); + /* OK, we need info for this table */ + if (tbloids->len > 1) + appendPQExpBufferChar(tbloids, ','); + appendPQExpBuffer(tbloids, "%u", tbinfo->dobj.catId.oid); + } + appendPQExpBufferChar(tbloids, '}'); - resetPQExpBuffer(query); - if (fout->remoteVersion >= 110000) - appendPQExpBuffer(query, - "SELECT tableoid, oid, conname, confrelid, conindid, " - "pg_catalog.pg_get_constraintdef(oid) AS condef " - "FROM pg_catalog.pg_constraint " - "WHERE conrelid = '%u'::pg_catalog.oid " - "AND conparentid = 0 " - "AND contype = 'f'", - tbinfo->dobj.catId.oid); - else - appendPQExpBuffer(query, - "SELECT tableoid, oid, conname, confrelid, 0 as conindid, " - "pg_catalog.pg_get_constraintdef(oid) AS condef " - "FROM pg_catalog.pg_constraint " - "WHERE conrelid = '%u'::pg_catalog.oid " - "AND contype = 'f'", - tbinfo->dobj.catId.oid); - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + appendPQExpBufferStr(query, + "SELECT c.tableoid, c.oid, " + "conrelid, conname, confrelid, "); + if (fout->remoteVersion >= 110000) + appendPQExpBufferStr(query, "conindid, "); + else + appendPQExpBufferStr(query, "0 AS conindid, "); + appendPQExpBuffer(query, + "pg_catalog.pg_get_constraintdef(c.oid) AS condef\n" + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" + "WHERE contype = 'f' ", + tbloids->data); + if (fout->remoteVersion >= 110000) + appendPQExpBufferStr(query, + "AND conparentid = 0 "); + appendPQExpBufferStr(query, + "ORDER BY conrelid, conname"); - ntups = PQntuples(res); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - i_contableoid = PQfnumber(res, "tableoid"); - i_conoid = PQfnumber(res, "oid"); - i_conname = PQfnumber(res, "conname"); - i_confrelid = PQfnumber(res, "confrelid"); - i_conindid = PQfnumber(res, "conindid"); - i_condef = PQfnumber(res, "condef"); + ntups = PQntuples(res); - constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo)); + i_contableoid = PQfnumber(res, "tableoid"); + i_conoid = PQfnumber(res, "oid"); + i_conrelid = PQfnumber(res, "conrelid"); + i_conname = PQfnumber(res, "conname"); + i_confrelid = PQfnumber(res, "confrelid"); + i_conindid = PQfnumber(res, "conindid"); + i_condef = PQfnumber(res, "condef"); - for (j = 0; j < ntups; j++) - { - TableInfo *reftable; - - constrinfo[j].dobj.objType = DO_FK_CONSTRAINT; - constrinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_contableoid)); - constrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_conoid)); - AssignDumpId(&constrinfo[j].dobj); - constrinfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_conname)); - constrinfo[j].dobj.namespace = tbinfo->dobj.namespace; - constrinfo[j].contable = tbinfo; - constrinfo[j].condomain = NULL; - constrinfo[j].contype = 'f'; - constrinfo[j].condef = pg_strdup(PQgetvalue(res, j, i_condef)); - constrinfo[j].confrelid = atooid(PQgetvalue(res, j, i_confrelid)); - constrinfo[j].conindex = 0; - constrinfo[j].condeferrable = false; - constrinfo[j].condeferred = false; - constrinfo[j].conislocal = true; - constrinfo[j].separate = true; + constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo)); - /* - * Restoring an FK that points to a partitioned table requires - * that all partition indexes have been attached beforehand. - * Ensure that happens by making the constraint depend on each - * index partition attach object. - */ - reftable = findTableByOid(constrinfo[j].confrelid); - if (reftable && reftable->relkind == RELKIND_PARTITIONED_TABLE) + curtblindx = -1; + for (int j = 0; j < ntups; j++) + { + Oid conrelid = atooid(PQgetvalue(res, j, i_conrelid)); + TableInfo *reftable; + + /* + * Locate the associated TableInfo; we rely on tblinfo[] being in OID + * order. + */ + if (tbinfo == NULL || tbinfo->dobj.catId.oid != conrelid) + { + while (++curtblindx < numTables) { - Oid indexOid = atooid(PQgetvalue(res, j, i_conindid)); + tbinfo = &tblinfo[curtblindx]; + if (tbinfo->dobj.catId.oid == conrelid) + break; + } + if (curtblindx >= numTables) + fatal("unrecognized table OID %u", conrelid); + } + + constrinfo[j].dobj.objType = DO_FK_CONSTRAINT; + constrinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_contableoid)); + constrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_conoid)); + AssignDumpId(&constrinfo[j].dobj); + constrinfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_conname)); + constrinfo[j].dobj.namespace = tbinfo->dobj.namespace; + constrinfo[j].contable = tbinfo; + constrinfo[j].condomain = NULL; + constrinfo[j].contype = 'f'; + constrinfo[j].condef = pg_strdup(PQgetvalue(res, j, i_condef)); + constrinfo[j].confrelid = atooid(PQgetvalue(res, j, i_confrelid)); + constrinfo[j].conindex = 0; + constrinfo[j].condeferrable = false; + constrinfo[j].condeferred = false; + constrinfo[j].conislocal = true; + constrinfo[j].separate = true; + + /* + * Restoring an FK that points to a partitioned table requires that + * all partition indexes have been attached beforehand. Ensure that + * happens by making the constraint depend on each index partition + * attach object. + */ + reftable = findTableByOid(constrinfo[j].confrelid); + if (reftable && reftable->relkind == RELKIND_PARTITIONED_TABLE) + { + Oid indexOid = atooid(PQgetvalue(res, j, i_conindid)); - if (indexOid != InvalidOid) + if (indexOid != InvalidOid) + { + for (int k = 0; k < reftable->numIndexes; k++) { - for (int k = 0; k < reftable->numIndexes; k++) - { - IndxInfo *refidx; + IndxInfo *refidx; - /* not our index? */ - if (reftable->indexes[k].dobj.catId.oid != indexOid) - continue; + /* not our index? */ + if (reftable->indexes[k].dobj.catId.oid != indexOid) + continue; - refidx = &reftable->indexes[k]; - addConstrChildIdxDeps(&constrinfo[j].dobj, refidx); - break; - } + refidx = &reftable->indexes[k]; + addConstrChildIdxDeps(&constrinfo[j].dobj, refidx); + break; } } } - - PQclear(res); } + PQclear(res); + destroyPQExpBuffer(query); + destroyPQExpBuffer(tbloids); } /* @@ -7436,13 +7517,15 @@ getRules(Archive *fout, int *numRules) void getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) { - int i, - j; PQExpBuffer query = createPQExpBuffer(); + PQExpBuffer tbloids = createPQExpBuffer(); PGresult *res; + int ntups; + int curtblindx; TriggerInfo *tginfo; int i_tableoid, i_oid, + i_tgrelid, i_tgname, i_tgfname, i_tgtype, @@ -7457,9 +7540,17 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) i_tgdeferrable, i_tginitdeferred, i_tgdef; - int ntups; - for (i = 0; i < numTables; i++) + /* + * We want to perform just one query against pg_trigger. However, we + * mustn't try to select every row of the catalog and then sort it out on + * the client side, because some of the server-side functions we need + * would be unsafe to apply to tables we don't have lock on. Hence, we + * build an array of the OIDs of tables we care about (and now have lock + * on!), and use a WHERE clause to constrain which rows are selected. + */ + appendPQExpBufferChar(tbloids, '{'); + for (int i = 0; i < numTables; i++) { TableInfo *tbinfo = &tblinfo[i]; @@ -7467,143 +7558,178 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) !(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)) continue; - pg_log_info("reading triggers for table \"%s.%s\"", - tbinfo->dobj.namespace->dobj.name, - tbinfo->dobj.name); + /* OK, we need info for this table */ + if (tbloids->len > 1) + appendPQExpBufferChar(tbloids, ','); + appendPQExpBuffer(tbloids, "%u", tbinfo->dobj.catId.oid); + } + appendPQExpBufferChar(tbloids, '}'); - resetPQExpBuffer(query); - if (fout->remoteVersion >= 130000) - { - /* - * NB: think not to use pretty=true in pg_get_triggerdef. It - * could result in non-forward-compatible dumps of WHEN clauses - * due to under-parenthesization. - * - * NB: We need to see tgisinternal triggers in partitions, in case - * the tgenabled flag has been changed from the parent. - */ - appendPQExpBuffer(query, - "SELECT t.tgname, " - "t.tgfoid::pg_catalog.regproc AS tgfname, " - "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " - "t.tgenabled, t.tableoid, t.oid, t.tgisinternal " - "FROM pg_catalog.pg_trigger t " - "LEFT JOIN pg_catalog.pg_trigger u ON u.oid = t.tgparentid " - "WHERE t.tgrelid = '%u'::pg_catalog.oid " - "AND (NOT t.tgisinternal OR t.tgenabled != u.tgenabled)", - tbinfo->dobj.catId.oid); - } - else if (fout->remoteVersion >= 110000) - { - /* - * NB: We need to see tgisinternal triggers in partitions, in case - * the tgenabled flag has been changed from the parent. No - * tgparentid in version 11-12, so we have to match them via - * pg_depend. - * - * See above about pretty=true in pg_get_triggerdef. - */ - appendPQExpBuffer(query, - "SELECT t.tgname, " - "t.tgfoid::pg_catalog.regproc AS tgfname, " - "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " - "t.tgenabled, t.tableoid, t.oid, t.tgisinternal " - "FROM pg_catalog.pg_trigger t " - "LEFT JOIN pg_catalog.pg_depend AS d ON " - " d.classid = 'pg_catalog.pg_trigger'::pg_catalog.regclass AND " - " d.refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass AND " - " d.objid = t.oid " - "LEFT JOIN pg_catalog.pg_trigger AS pt ON pt.oid = refobjid " - "WHERE t.tgrelid = '%u'::pg_catalog.oid " - "AND (NOT t.tgisinternal%s)", - tbinfo->dobj.catId.oid, - tbinfo->ispartition ? - " OR t.tgenabled != pt.tgenabled" : ""); - } - else if (fout->remoteVersion >= 90000) - { - /* See above about pretty=true in pg_get_triggerdef */ - appendPQExpBuffer(query, - "SELECT t.tgname, " - "t.tgfoid::pg_catalog.regproc AS tgfname, " - "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " - "t.tgenabled, false as tgisinternal, " - "t.tableoid, t.oid " - "FROM pg_catalog.pg_trigger t " - "WHERE tgrelid = '%u'::pg_catalog.oid " - "AND NOT tgisinternal", - tbinfo->dobj.catId.oid); - } - else if (fout->remoteVersion >= 80300) - { - /* - * We ignore triggers that are tied to a foreign-key constraint - */ - appendPQExpBuffer(query, - "SELECT tgname, " - "tgfoid::pg_catalog.regproc AS tgfname, " - "tgtype, tgnargs, tgargs, tgenabled, " - "false as tgisinternal, " - "tgisconstraint, tgconstrname, tgdeferrable, " - "tgconstrrelid, tginitdeferred, tableoid, oid, " - "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " - "FROM pg_catalog.pg_trigger t " - "WHERE tgrelid = '%u'::pg_catalog.oid " - "AND tgconstraint = 0", - tbinfo->dobj.catId.oid); - } - else - { - /* - * We ignore triggers that are tied to a foreign-key constraint, - * but in these versions we have to grovel through pg_constraint - * to find out - */ - appendPQExpBuffer(query, - "SELECT tgname, " - "tgfoid::pg_catalog.regproc AS tgfname, " - "tgtype, tgnargs, tgargs, tgenabled, " - "false as tgisinternal, " - "tgisconstraint, tgconstrname, tgdeferrable, " - "tgconstrrelid, tginitdeferred, tableoid, oid, " - "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " - "FROM pg_catalog.pg_trigger t " - "WHERE tgrelid = '%u'::pg_catalog.oid " - "AND (NOT tgisconstraint " - " OR NOT EXISTS" - " (SELECT 1 FROM pg_catalog.pg_depend d " - " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)" - " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))", - tbinfo->dobj.catId.oid); - } + if (fout->remoteVersion >= 130000) + { + /* + * NB: think not to use pretty=true in pg_get_triggerdef. It could + * result in non-forward-compatible dumps of WHEN clauses due to + * under-parenthesization. + * + * NB: We need to see tgisinternal triggers in partitions, in case the + * tgenabled flag has been changed from the parent. + */ + appendPQExpBuffer(query, + "SELECT t.tgrelid, t.tgname, " + "t.tgfoid::pg_catalog.regproc AS tgfname, " + "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " + "t.tgenabled, t.tableoid, t.oid, t.tgisinternal\n" + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) " + "LEFT JOIN pg_catalog.pg_trigger u ON (u.oid = t.tgparentid) " + "WHERE (NOT t.tgisinternal OR t.tgenabled != u.tgenabled) " + "ORDER BY t.tgrelid, t.tgname", + tbloids->data); + } + else if (fout->remoteVersion >= 110000) + { + /* + * NB: We need to see tgisinternal triggers in partitions, in case the + * tgenabled flag has been changed from the parent. No tgparentid in + * version 11-12, so we have to match them via pg_depend. + * + * See above about pretty=true in pg_get_triggerdef. + */ + appendPQExpBuffer(query, + "SELECT t.tgrelid, t.tgname, " + "t.tgfoid::pg_catalog.regproc AS tgfname, " + "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " + "t.tgenabled, t.tableoid, t.oid, t.tgisinternal " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) " + "LEFT JOIN pg_catalog.pg_depend AS d ON " + " d.classid = 'pg_catalog.pg_trigger'::pg_catalog.regclass AND " + " d.refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass AND " + " d.objid = t.oid " + "LEFT JOIN pg_catalog.pg_trigger AS pt ON pt.oid = refobjid " + "WHERE (NOT t.tgisinternal OR t.tgenabled != pt.tgenabled) " + "ORDER BY t.tgrelid, t.tgname", + tbloids->data); + } + else if (fout->remoteVersion >= 90000) + { + /* See above about pretty=true in pg_get_triggerdef */ + appendPQExpBuffer(query, + "SELECT t.tgrelid, t.tgname, " + "t.tgfoid::pg_catalog.regproc AS tgfname, " + "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " + "t.tgenabled, false as tgisinternal, " + "t.tableoid, t.oid " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) " + "WHERE NOT tgisinternal " + "ORDER BY t.tgrelid, t.tgname", + tbloids->data); + } + else if (fout->remoteVersion >= 80300) + { + /* + * We ignore triggers that are tied to a foreign-key constraint + */ + appendPQExpBuffer(query, + "SELECT t.tgrelid, tgname, " + "tgfoid::pg_catalog.regproc AS tgfname, " + "tgtype, tgnargs, tgargs, tgenabled, " + "false as tgisinternal, " + "tgisconstraint, tgconstrname, tgdeferrable, " + "tgconstrrelid, tginitdeferred, t.tableoid, t.oid, " + "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) " + "WHERE tgconstraint = 0 " + "ORDER BY t.tgrelid, t.tgname", + tbloids->data); + } + else + { + /* + * We ignore triggers that are tied to a foreign-key constraint, but + * in these versions we have to grovel through pg_constraint to find + * out + */ + appendPQExpBuffer(query, + "SELECT t.tgrelid, tgname, " + "tgfoid::pg_catalog.regproc AS tgfname, " + "tgtype, tgnargs, tgargs, tgenabled, " + "false as tgisinternal, " + "tgisconstraint, tgconstrname, tgdeferrable, " + "tgconstrrelid, tginitdeferred, t.tableoid, t.oid, " + "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_trigger t ON (src.tbloid = t.tgrelid) " + "WHERE (NOT tgisconstraint " + " OR NOT EXISTS" + " (SELECT 1 FROM pg_catalog.pg_depend d " + " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) " + " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))" + "ORDER BY t.tgrelid, t.tgname", + tbloids->data); + } - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - ntups = PQntuples(res); + ntups = PQntuples(res); - i_tableoid = PQfnumber(res, "tableoid"); - i_oid = PQfnumber(res, "oid"); - i_tgname = PQfnumber(res, "tgname"); - i_tgfname = PQfnumber(res, "tgfname"); - i_tgtype = PQfnumber(res, "tgtype"); - i_tgnargs = PQfnumber(res, "tgnargs"); - i_tgargs = PQfnumber(res, "tgargs"); - i_tgisconstraint = PQfnumber(res, "tgisconstraint"); - i_tgconstrname = PQfnumber(res, "tgconstrname"); - i_tgconstrrelid = PQfnumber(res, "tgconstrrelid"); - i_tgconstrrelname = PQfnumber(res, "tgconstrrelname"); - i_tgenabled = PQfnumber(res, "tgenabled"); - i_tgisinternal = PQfnumber(res, "tgisinternal"); - i_tgdeferrable = PQfnumber(res, "tgdeferrable"); - i_tginitdeferred = PQfnumber(res, "tginitdeferred"); - i_tgdef = PQfnumber(res, "tgdef"); - - tginfo = (TriggerInfo *) pg_malloc(ntups * sizeof(TriggerInfo)); - - tbinfo->numTriggers = ntups; - tbinfo->triggers = tginfo; + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_tgrelid = PQfnumber(res, "tgrelid"); + i_tgname = PQfnumber(res, "tgname"); + i_tgfname = PQfnumber(res, "tgfname"); + i_tgtype = PQfnumber(res, "tgtype"); + i_tgnargs = PQfnumber(res, "tgnargs"); + i_tgargs = PQfnumber(res, "tgargs"); + i_tgisconstraint = PQfnumber(res, "tgisconstraint"); + i_tgconstrname = PQfnumber(res, "tgconstrname"); + i_tgconstrrelid = PQfnumber(res, "tgconstrrelid"); + i_tgconstrrelname = PQfnumber(res, "tgconstrrelname"); + i_tgenabled = PQfnumber(res, "tgenabled"); + i_tgisinternal = PQfnumber(res, "tgisinternal"); + i_tgdeferrable = PQfnumber(res, "tgdeferrable"); + i_tginitdeferred = PQfnumber(res, "tginitdeferred"); + i_tgdef = PQfnumber(res, "tgdef"); + + tginfo = (TriggerInfo *) pg_malloc(ntups * sizeof(TriggerInfo)); - for (j = 0; j < ntups; j++) + /* + * Outer loop iterates once per table, not once per row. Incrementing of + * j is handled by the inner loop. + */ + curtblindx = -1; + for (int j = 0; j < ntups;) + { + Oid tgrelid = atooid(PQgetvalue(res, j, i_tgrelid)); + TableInfo *tbinfo = NULL; + int numtrigs; + + /* Count rows for this table */ + for (numtrigs = 1; numtrigs < ntups - j; numtrigs++) + if (atooid(PQgetvalue(res, j + numtrigs, i_tgrelid)) != tgrelid) + break; + + /* + * Locate the associated TableInfo; we rely on tblinfo[] being in OID + * order. + */ + while (++curtblindx < numTables) + { + tbinfo = &tblinfo[curtblindx]; + if (tbinfo->dobj.catId.oid == tgrelid) + break; + } + if (curtblindx >= numTables) + fatal("unrecognized table OID %u", tgrelid); + + /* Save data for this table */ + tbinfo->triggers = tginfo + j; + tbinfo->numTriggers = numtrigs; + + for (int c = 0; c < numtrigs; c++, j++) { tginfo[j].dobj.objType = DO_TRIGGER; tginfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid)); @@ -7666,11 +7792,12 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) } } } - - PQclear(res); } + PQclear(res); + destroyPQExpBuffer(query); + destroyPQExpBuffer(tbloids); } /* @@ -8124,12 +8251,6 @@ getTransforms(Archive *fout, int *numTransforms) * for each interesting table, read info about its attributes * (names, types, default values, CHECK constraints, etc) * - * This is implemented in a very inefficient way right now, looping - * through the tblinfo and doing a join per table to find the attrs and their - * types. However, because we want type names and so forth to be named - * relative to the schema of each table, we couldn't do it in just one - * query. (Maybe one query per schema?) - * * modifies tblinfo */ void @@ -8137,6 +8258,12 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) { DumpOptions *dopt = fout->dopt; PQExpBuffer q = createPQExpBuffer(); + PQExpBuffer tbloids = createPQExpBuffer(); + PQExpBuffer checkoids = createPQExpBuffer(); + PGresult *res; + int ntups; + int curtblindx; + int i_attrelid; int i_attnum; int i_attname; int i_atttypname; @@ -8158,12 +8285,21 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) int i_attmissingval; int i_atthasdef; + /* + * We want to perform just one query against pg_attribute, and then just + * one against pg_attrdef (for DEFAULTs) and one against pg_constraint + * (for CHECK constraints). However, we mustn't try to select every row + * of those catalogs and then sort it out on the client side, because some + * of the server-side functions we need would be unsafe to apply to tables + * we don't have lock on. Hence, we build an array of the OIDs of tables + * we care about (and now have lock on!), and use a WHERE clause to + * constrain which rows are selected. + */ + appendPQExpBufferChar(tbloids, '{'); + appendPQExpBufferChar(checkoids, '{'); for (int i = 0; i < numTables; i++) { TableInfo *tbinfo = &tblinfo[i]; - PGresult *res; - int ntups; - bool hasdefaults; /* Don't bother to collect info for sequences */ if (tbinfo->relkind == RELKIND_SEQUENCE) @@ -8173,390 +8309,499 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) if (!tbinfo->interesting) continue; - /* find all the user attributes and their types */ + /* OK, we need info for this table */ + if (tbloids->len > 1) + appendPQExpBufferChar(tbloids, ','); + appendPQExpBuffer(tbloids, "%u", tbinfo->dobj.catId.oid); + + if (tbinfo->ncheck > 0) + { + /* Also make a list of the ones with check constraints */ + if (checkoids->len > 1) + appendPQExpBufferChar(checkoids, ','); + appendPQExpBuffer(checkoids, "%u", tbinfo->dobj.catId.oid); + } + } + appendPQExpBufferChar(tbloids, '}'); + appendPQExpBufferChar(checkoids, '}'); + + /* find all the user attributes and their types */ + appendPQExpBufferStr(q, + "SELECT\n" + "a.attrelid,\n" + "a.attnum,\n" + "a.attname,\n" + "a.atttypmod,\n" + "a.attstattarget,\n" + "a.attstorage,\n" + "t.typstorage,\n" + "a.attnotnull,\n" + "a.atthasdef,\n" + "a.attisdropped,\n" + "a.attlen,\n" + "a.attalign,\n" + "a.attislocal,\n" + "pg_catalog.format_type(t.oid, a.atttypmod) AS atttypname,\n"); + if (fout->remoteVersion >= 90000) + appendPQExpBufferStr(q, + "array_to_string(a.attoptions, ', ') AS attoptions,\n"); + else + appendPQExpBufferStr(q, + "'' AS attoptions,\n"); + + if (fout->remoteVersion >= 90100) + { /* - * we must read the attribute names in attribute number order! because - * we will use the attnum to index into the attnames array later. + * Since we only want to dump COLLATE clauses for attributes whose + * collation is different from their type's default, we use a CASE + * here to suppress uninteresting attcollations cheaply. */ - pg_log_info("finding the columns and types of table \"%s.%s\"", - tbinfo->dobj.namespace->dobj.name, - tbinfo->dobj.name); + appendPQExpBufferStr(q, + "CASE WHEN a.attcollation <> t.typcollation " + "THEN a.attcollation ELSE 0 END AS attcollation,\n"); + } + else + appendPQExpBufferStr(q, + "0 AS attcollation,\n"); - resetPQExpBuffer(q); + if (fout->remoteVersion >= 140000) + appendPQExpBufferStr(q, + "a.attcompression AS attcompression,\n"); + else + appendPQExpBufferStr(q, + "'' AS attcompression,\n"); + if (fout->remoteVersion >= 90200) appendPQExpBufferStr(q, - "SELECT\n" - "a.attnum,\n" - "a.attname,\n" - "a.atttypmod,\n" - "a.attstattarget,\n" - "a.attstorage,\n" - "t.typstorage,\n" - "a.attnotnull,\n" - "a.atthasdef,\n" - "a.attisdropped,\n" - "a.attlen,\n" - "a.attalign,\n" - "a.attislocal,\n" - "pg_catalog.format_type(t.oid, a.atttypmod) AS atttypname,\n"); - - if (fout->remoteVersion >= 90000) - appendPQExpBufferStr(q, - "array_to_string(a.attoptions, ', ') AS attoptions,\n"); - else - appendPQExpBufferStr(q, - "'' AS attoptions,\n"); + "pg_catalog.array_to_string(ARRAY(" + "SELECT pg_catalog.quote_ident(option_name) || " + "' ' || pg_catalog.quote_literal(option_value) " + "FROM pg_catalog.pg_options_to_table(attfdwoptions) " + "ORDER BY option_name" + "), E',\n ') AS attfdwoptions,\n"); + else + appendPQExpBufferStr(q, + "'' AS attfdwoptions,\n"); - if (fout->remoteVersion >= 90100) - { - /* - * Since we only want to dump COLLATE clauses for attributes whose - * collation is different from their type's default, we use a CASE - * here to suppress uninteresting attcollations cheaply. - */ - appendPQExpBufferStr(q, - "CASE WHEN a.attcollation <> t.typcollation " - "THEN a.attcollation ELSE 0 END AS attcollation,\n"); - } - else - appendPQExpBufferStr(q, - "0 AS attcollation,\n"); + if (fout->remoteVersion >= 100000) + appendPQExpBufferStr(q, + "a.attidentity,\n"); + else + appendPQExpBufferStr(q, + "'' AS attidentity,\n"); - if (fout->remoteVersion >= 140000) - appendPQExpBuffer(q, - "a.attcompression AS attcompression,\n"); - else - appendPQExpBuffer(q, - "'' AS attcompression,\n"); + if (fout->remoteVersion >= 110000) + appendPQExpBufferStr(q, + "CASE WHEN a.atthasmissing AND NOT a.attisdropped " + "THEN a.attmissingval ELSE null END AS attmissingval,\n"); + else + appendPQExpBufferStr(q, + "NULL AS attmissingval,\n"); - if (fout->remoteVersion >= 90200) - appendPQExpBufferStr(q, - "pg_catalog.array_to_string(ARRAY(" - "SELECT pg_catalog.quote_ident(option_name) || " - "' ' || pg_catalog.quote_literal(option_value) " - "FROM pg_catalog.pg_options_to_table(attfdwoptions) " - "ORDER BY option_name" - "), E',\n ') AS attfdwoptions,\n"); - else - appendPQExpBufferStr(q, - "'' AS attfdwoptions,\n"); + if (fout->remoteVersion >= 120000) + appendPQExpBufferStr(q, + "a.attgenerated\n"); + else + appendPQExpBufferStr(q, + "'' AS attgenerated\n"); - if (fout->remoteVersion >= 100000) - appendPQExpBufferStr(q, - "a.attidentity,\n"); - else - appendPQExpBufferStr(q, - "'' AS attidentity,\n"); + /* need left join to pg_type to not fail on dropped columns ... */ + appendPQExpBuffer(q, + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_attribute a ON (src.tbloid = a.attrelid) " + "LEFT JOIN pg_catalog.pg_type t " + "ON (a.atttypid = t.oid)\n" + "WHERE a.attnum > 0::pg_catalog.int2\n" + "ORDER BY a.attrelid, a.attnum", + tbloids->data); - if (fout->remoteVersion >= 110000) - appendPQExpBufferStr(q, - "CASE WHEN a.atthasmissing AND NOT a.attisdropped " - "THEN a.attmissingval ELSE null END AS attmissingval,\n"); - else - appendPQExpBufferStr(q, - "NULL AS attmissingval,\n"); + res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); - if (fout->remoteVersion >= 120000) - appendPQExpBufferStr(q, - "a.attgenerated\n"); - else - appendPQExpBufferStr(q, - "'' AS attgenerated\n"); + ntups = PQntuples(res); - /* need left join here to not fail on dropped columns ... */ - appendPQExpBuffer(q, - "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " - "ON a.atttypid = t.oid\n" - "WHERE a.attrelid = '%u'::pg_catalog.oid " - "AND a.attnum > 0::pg_catalog.int2\n" - "ORDER BY a.attnum", - tbinfo->dobj.catId.oid); + i_attrelid = PQfnumber(res, "attrelid"); + i_attnum = PQfnumber(res, "attnum"); + i_attname = PQfnumber(res, "attname"); + i_atttypname = PQfnumber(res, "atttypname"); + i_atttypmod = PQfnumber(res, "atttypmod"); + i_attstattarget = PQfnumber(res, "attstattarget"); + i_attstorage = PQfnumber(res, "attstorage"); + i_typstorage = PQfnumber(res, "typstorage"); + i_attidentity = PQfnumber(res, "attidentity"); + i_attgenerated = PQfnumber(res, "attgenerated"); + i_attisdropped = PQfnumber(res, "attisdropped"); + i_attlen = PQfnumber(res, "attlen"); + i_attalign = PQfnumber(res, "attalign"); + i_attislocal = PQfnumber(res, "attislocal"); + i_attnotnull = PQfnumber(res, "attnotnull"); + i_attoptions = PQfnumber(res, "attoptions"); + i_attcollation = PQfnumber(res, "attcollation"); + i_attcompression = PQfnumber(res, "attcompression"); + i_attfdwoptions = PQfnumber(res, "attfdwoptions"); + i_attmissingval = PQfnumber(res, "attmissingval"); + i_atthasdef = PQfnumber(res, "atthasdef"); - res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); + /* Within the next loop, we'll accumulate OIDs of tables with defaults */ + resetPQExpBuffer(tbloids); + appendPQExpBufferChar(tbloids, '{'); - ntups = PQntuples(res); + /* + * Outer loop iterates once per table, not once per row. Incrementing of + * r is handled by the inner loop. + */ + curtblindx = -1; + for (int r = 0; r < ntups;) + { + Oid attrelid = atooid(PQgetvalue(res, r, i_attrelid)); + TableInfo *tbinfo = NULL; + int numatts; + bool hasdefaults; + + /* Count rows for this table */ + for (numatts = 1; numatts < ntups - r; numatts++) + if (atooid(PQgetvalue(res, r + numatts, i_attrelid)) != attrelid) + break; - tbinfo->numatts = ntups; - tbinfo->attnames = (char **) pg_malloc(ntups * sizeof(char *)); - tbinfo->atttypnames = (char **) pg_malloc(ntups * sizeof(char *)); - tbinfo->atttypmod = (int *) pg_malloc(ntups * sizeof(int)); - tbinfo->attstattarget = (int *) pg_malloc(ntups * sizeof(int)); - tbinfo->attstorage = (char *) pg_malloc(ntups * sizeof(char)); - tbinfo->typstorage = (char *) pg_malloc(ntups * sizeof(char)); - tbinfo->attidentity = (char *) pg_malloc(ntups * sizeof(char)); - tbinfo->attgenerated = (char *) pg_malloc(ntups * sizeof(char)); - tbinfo->attisdropped = (bool *) pg_malloc(ntups * sizeof(bool)); - tbinfo->attlen = (int *) pg_malloc(ntups * sizeof(int)); - tbinfo->attalign = (char *) pg_malloc(ntups * sizeof(char)); - tbinfo->attislocal = (bool *) pg_malloc(ntups * sizeof(bool)); - tbinfo->attoptions = (char **) pg_malloc(ntups * sizeof(char *)); - tbinfo->attcollation = (Oid *) pg_malloc(ntups * sizeof(Oid)); - tbinfo->attcompression = (char *) pg_malloc(ntups * sizeof(char)); - tbinfo->attfdwoptions = (char **) pg_malloc(ntups * sizeof(char *)); - tbinfo->attmissingval = (char **) pg_malloc(ntups * sizeof(char *)); - tbinfo->notnull = (bool *) pg_malloc(ntups * sizeof(bool)); - tbinfo->inhNotNull = (bool *) pg_malloc(ntups * sizeof(bool)); - tbinfo->attrdefs = (AttrDefInfo **) pg_malloc(ntups * sizeof(AttrDefInfo *)); + /* + * Locate the associated TableInfo; we rely on tblinfo[] being in OID + * order. + */ + while (++curtblindx < numTables) + { + tbinfo = &tblinfo[curtblindx]; + if (tbinfo->dobj.catId.oid == attrelid) + break; + } + if (curtblindx >= numTables) + fatal("unrecognized table OID %u", attrelid); + /* cross-check that we only got requested tables */ + if (tbinfo->relkind == RELKIND_SEQUENCE || + !tbinfo->interesting) + fatal("unexpected column data for table \"%s\"", + tbinfo->dobj.name); + + /* Save data for this table */ + tbinfo->numatts = numatts; + tbinfo->attnames = (char **) pg_malloc(numatts * sizeof(char *)); + tbinfo->atttypnames = (char **) pg_malloc(numatts * sizeof(char *)); + tbinfo->atttypmod = (int *) pg_malloc(numatts * sizeof(int)); + tbinfo->attstattarget = (int *) pg_malloc(numatts * sizeof(int)); + tbinfo->attstorage = (char *) pg_malloc(numatts * sizeof(char)); + tbinfo->typstorage = (char *) pg_malloc(numatts * sizeof(char)); + tbinfo->attidentity = (char *) pg_malloc(numatts * sizeof(char)); + tbinfo->attgenerated = (char *) pg_malloc(numatts * sizeof(char)); + tbinfo->attisdropped = (bool *) pg_malloc(numatts * sizeof(bool)); + tbinfo->attlen = (int *) pg_malloc(numatts * sizeof(int)); + tbinfo->attalign = (char *) pg_malloc(numatts * sizeof(char)); + tbinfo->attislocal = (bool *) pg_malloc(numatts * sizeof(bool)); + tbinfo->attoptions = (char **) pg_malloc(numatts * sizeof(char *)); + tbinfo->attcollation = (Oid *) pg_malloc(numatts * sizeof(Oid)); + tbinfo->attcompression = (char *) pg_malloc(numatts * sizeof(char)); + tbinfo->attfdwoptions = (char **) pg_malloc(numatts * sizeof(char *)); + tbinfo->attmissingval = (char **) pg_malloc(numatts * sizeof(char *)); + tbinfo->notnull = (bool *) pg_malloc(numatts * sizeof(bool)); + tbinfo->inhNotNull = (bool *) pg_malloc(numatts * sizeof(bool)); + tbinfo->attrdefs = (AttrDefInfo **) pg_malloc(numatts * sizeof(AttrDefInfo *)); hasdefaults = false; - i_attnum = PQfnumber(res, "attnum"); - i_attname = PQfnumber(res, "attname"); - i_atttypname = PQfnumber(res, "atttypname"); - i_atttypmod = PQfnumber(res, "atttypmod"); - i_attstattarget = PQfnumber(res, "attstattarget"); - i_attstorage = PQfnumber(res, "attstorage"); - i_typstorage = PQfnumber(res, "typstorage"); - i_attidentity = PQfnumber(res, "attidentity"); - i_attgenerated = PQfnumber(res, "attgenerated"); - i_attisdropped = PQfnumber(res, "attisdropped"); - i_attlen = PQfnumber(res, "attlen"); - i_attalign = PQfnumber(res, "attalign"); - i_attislocal = PQfnumber(res, "attislocal"); - i_attnotnull = PQfnumber(res, "attnotnull"); - i_attoptions = PQfnumber(res, "attoptions"); - i_attcollation = PQfnumber(res, "attcollation"); - i_attcompression = PQfnumber(res, "attcompression"); - i_attfdwoptions = PQfnumber(res, "attfdwoptions"); - i_attmissingval = PQfnumber(res, "attmissingval"); - i_atthasdef = PQfnumber(res, "atthasdef"); - - for (int j = 0; j < ntups; j++) + for (int j = 0; j < numatts; j++, r++) { - if (j + 1 != atoi(PQgetvalue(res, j, i_attnum))) + if (j + 1 != atoi(PQgetvalue(res, r, i_attnum))) fatal("invalid column numbering in table \"%s\"", tbinfo->dobj.name); - tbinfo->attnames[j] = pg_strdup(PQgetvalue(res, j, i_attname)); - tbinfo->atttypnames[j] = pg_strdup(PQgetvalue(res, j, i_atttypname)); - tbinfo->atttypmod[j] = atoi(PQgetvalue(res, j, i_atttypmod)); - tbinfo->attstattarget[j] = atoi(PQgetvalue(res, j, i_attstattarget)); - tbinfo->attstorage[j] = *(PQgetvalue(res, j, i_attstorage)); - tbinfo->typstorage[j] = *(PQgetvalue(res, j, i_typstorage)); - tbinfo->attidentity[j] = *(PQgetvalue(res, j, i_attidentity)); - tbinfo->attgenerated[j] = *(PQgetvalue(res, j, i_attgenerated)); + tbinfo->attnames[j] = pg_strdup(PQgetvalue(res, r, i_attname)); + tbinfo->atttypnames[j] = pg_strdup(PQgetvalue(res, r, i_atttypname)); + tbinfo->atttypmod[j] = atoi(PQgetvalue(res, r, i_atttypmod)); + tbinfo->attstattarget[j] = atoi(PQgetvalue(res, r, i_attstattarget)); + tbinfo->attstorage[j] = *(PQgetvalue(res, r, i_attstorage)); + tbinfo->typstorage[j] = *(PQgetvalue(res, r, i_typstorage)); + tbinfo->attidentity[j] = *(PQgetvalue(res, r, i_attidentity)); + tbinfo->attgenerated[j] = *(PQgetvalue(res, r, i_attgenerated)); tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == ATTRIBUTE_IDENTITY_ALWAYS); - tbinfo->attisdropped[j] = (PQgetvalue(res, j, i_attisdropped)[0] == 't'); - tbinfo->attlen[j] = atoi(PQgetvalue(res, j, i_attlen)); - tbinfo->attalign[j] = *(PQgetvalue(res, j, i_attalign)); - tbinfo->attislocal[j] = (PQgetvalue(res, j, i_attislocal)[0] == 't'); - tbinfo->notnull[j] = (PQgetvalue(res, j, i_attnotnull)[0] == 't'); - tbinfo->attoptions[j] = pg_strdup(PQgetvalue(res, j, i_attoptions)); - tbinfo->attcollation[j] = atooid(PQgetvalue(res, j, i_attcollation)); - tbinfo->attcompression[j] = *(PQgetvalue(res, j, i_attcompression)); - tbinfo->attfdwoptions[j] = pg_strdup(PQgetvalue(res, j, i_attfdwoptions)); - tbinfo->attmissingval[j] = pg_strdup(PQgetvalue(res, j, i_attmissingval)); + tbinfo->attisdropped[j] = (PQgetvalue(res, r, i_attisdropped)[0] == 't'); + tbinfo->attlen[j] = atoi(PQgetvalue(res, r, i_attlen)); + tbinfo->attalign[j] = *(PQgetvalue(res, r, i_attalign)); + tbinfo->attislocal[j] = (PQgetvalue(res, r, i_attislocal)[0] == 't'); + tbinfo->notnull[j] = (PQgetvalue(res, r, i_attnotnull)[0] == 't'); + tbinfo->attoptions[j] = pg_strdup(PQgetvalue(res, r, i_attoptions)); + tbinfo->attcollation[j] = atooid(PQgetvalue(res, r, i_attcollation)); + tbinfo->attcompression[j] = *(PQgetvalue(res, r, i_attcompression)); + tbinfo->attfdwoptions[j] = pg_strdup(PQgetvalue(res, r, i_attfdwoptions)); + tbinfo->attmissingval[j] = pg_strdup(PQgetvalue(res, r, i_attmissingval)); tbinfo->attrdefs[j] = NULL; /* fix below */ - if (PQgetvalue(res, j, i_atthasdef)[0] == 't') + if (PQgetvalue(res, r, i_atthasdef)[0] == 't') hasdefaults = true; /* these flags will be set in flagInhAttrs() */ tbinfo->inhNotNull[j] = false; } - PQclear(res); - - /* - * Get info about column defaults. This is skipped for a data-only - * dump, as it is only needed for table schemas. - */ - if (!dopt->dataOnly && hasdefaults) + if (hasdefaults) { - AttrDefInfo *attrdefs; - int numDefaults; - - pg_log_info("finding default expressions of table \"%s.%s\"", - tbinfo->dobj.namespace->dobj.name, - tbinfo->dobj.name); - - printfPQExpBuffer(q, "SELECT tableoid, oid, adnum, " - "pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc " - "FROM pg_catalog.pg_attrdef " - "WHERE adrelid = '%u'::pg_catalog.oid", - tbinfo->dobj.catId.oid); - - res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); + /* Collect OIDs of interesting tables that have defaults */ + if (tbloids->len > 1) + appendPQExpBufferChar(tbloids, ','); + appendPQExpBuffer(tbloids, "%u", tbinfo->dobj.catId.oid); + } + } - numDefaults = PQntuples(res); - attrdefs = (AttrDefInfo *) pg_malloc(numDefaults * sizeof(AttrDefInfo)); + PQclear(res); - for (int j = 0; j < numDefaults; j++) - { - int adnum; + /* + * Now get info about column defaults. This is skipped for a data-only + * dump, as it is only needed for table schemas. + */ + if (!dopt->dataOnly && tbloids->len > 1) + { + AttrDefInfo *attrdefs; + int numDefaults; + TableInfo *tbinfo = NULL; - adnum = atoi(PQgetvalue(res, j, 2)); + pg_log_info("finding table default expressions"); - if (adnum <= 0 || adnum > ntups) - fatal("invalid adnum value %d for table \"%s\"", - adnum, tbinfo->dobj.name); + appendPQExpBufferChar(tbloids, '}'); - /* - * dropped columns shouldn't have defaults, but just in case, - * ignore 'em - */ - if (tbinfo->attisdropped[adnum - 1]) - continue; + printfPQExpBuffer(q, "SELECT a.tableoid, a.oid, adrelid, adnum, " + "pg_catalog.pg_get_expr(adbin, adrelid) AS adsrc\n" + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_attrdef a ON (src.tbloid = a.adrelid)\n" + "ORDER BY a.adrelid, a.adnum", + tbloids->data); - attrdefs[j].dobj.objType = DO_ATTRDEF; - attrdefs[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, 0)); - attrdefs[j].dobj.catId.oid = atooid(PQgetvalue(res, j, 1)); - AssignDumpId(&attrdefs[j].dobj); - attrdefs[j].adtable = tbinfo; - attrdefs[j].adnum = adnum; - attrdefs[j].adef_expr = pg_strdup(PQgetvalue(res, j, 3)); + res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); - attrdefs[j].dobj.name = pg_strdup(tbinfo->dobj.name); - attrdefs[j].dobj.namespace = tbinfo->dobj.namespace; + numDefaults = PQntuples(res); + attrdefs = (AttrDefInfo *) pg_malloc(numDefaults * sizeof(AttrDefInfo)); - attrdefs[j].dobj.dump = tbinfo->dobj.dump; + curtblindx = -1; + for (int j = 0; j < numDefaults; j++) + { + Oid adtableoid = atooid(PQgetvalue(res, j, 0)); + Oid adoid = atooid(PQgetvalue(res, j, 1)); + Oid adrelid = atooid(PQgetvalue(res, j, 2)); + int adnum = atoi(PQgetvalue(res, j, 3)); + char *adsrc = PQgetvalue(res, j, 4); - /* - * Figure out whether the default/generation expression should - * be dumped as part of the main CREATE TABLE (or similar) - * command or as a separate ALTER TABLE (or similar) command. - * The preference is to put it into the CREATE command, but in - * some cases that's not possible. - */ - if (tbinfo->attgenerated[adnum - 1]) - { - /* - * Column generation expressions cannot be dumped - * separately, because there is no syntax for it. The - * !shouldPrintColumn case below will be tempted to set - * them to separate if they are attached to an inherited - * column without a local definition, but that would be - * wrong and unnecessary, because generation expressions - * are always inherited, so there is no need to set them - * again in child tables, and there is no syntax for it - * either. By setting separate to false here we prevent - * the "default" from being processed as its own dumpable - * object, and flagInhAttrs() will remove it from the - * table when it detects that it belongs to an inherited - * column. - */ - attrdefs[j].separate = false; - } - else if (tbinfo->relkind == RELKIND_VIEW) - { - /* - * Defaults on a VIEW must always be dumped as separate - * ALTER TABLE commands. - */ - attrdefs[j].separate = true; - } - else if (!shouldPrintColumn(dopt, tbinfo, adnum - 1)) - { - /* column will be suppressed, print default separately */ - attrdefs[j].separate = true; - } - else + /* + * Locate the associated TableInfo; we rely on tblinfo[] being in + * OID order. + */ + if (tbinfo == NULL || tbinfo->dobj.catId.oid != adrelid) + { + while (++curtblindx < numTables) { - attrdefs[j].separate = false; + tbinfo = &tblinfo[curtblindx]; + if (tbinfo->dobj.catId.oid == adrelid) + break; } + if (curtblindx >= numTables) + fatal("unrecognized table OID %u", adrelid); + } - if (!attrdefs[j].separate) - { - /* - * Mark the default as needing to appear before the table, - * so that any dependencies it has must be emitted before - * the CREATE TABLE. If this is not possible, we'll - * change to "separate" mode while sorting dependencies. - */ - addObjectDependency(&tbinfo->dobj, - attrdefs[j].dobj.dumpId); - } + if (adnum <= 0 || adnum > tbinfo->numatts) + fatal("invalid adnum value %d for table \"%s\"", + adnum, tbinfo->dobj.name); - tbinfo->attrdefs[adnum - 1] = &attrdefs[j]; - } - PQclear(res); - } + /* + * dropped columns shouldn't have defaults, but just in case, + * ignore 'em + */ + if (tbinfo->attisdropped[adnum - 1]) + continue; - /* - * Get info about table CHECK constraints. This is skipped for a - * data-only dump, as it is only needed for table schemas. - */ - if (tbinfo->ncheck > 0 && !dopt->dataOnly) - { - ConstraintInfo *constrs; - int numConstrs; + attrdefs[j].dobj.objType = DO_ATTRDEF; + attrdefs[j].dobj.catId.tableoid = adtableoid; + attrdefs[j].dobj.catId.oid = adoid; + AssignDumpId(&attrdefs[j].dobj); + attrdefs[j].adtable = tbinfo; + attrdefs[j].adnum = adnum; + attrdefs[j].adef_expr = pg_strdup(adsrc); + + attrdefs[j].dobj.name = pg_strdup(tbinfo->dobj.name); + attrdefs[j].dobj.namespace = tbinfo->dobj.namespace; - pg_log_info("finding check constraints for table \"%s.%s\"", - tbinfo->dobj.namespace->dobj.name, - tbinfo->dobj.name); + attrdefs[j].dobj.dump = tbinfo->dobj.dump; - resetPQExpBuffer(q); - if (fout->remoteVersion >= 90200) + /* + * Figure out whether the default/generation expression should be + * dumped as part of the main CREATE TABLE (or similar) command or + * as a separate ALTER TABLE (or similar) command. The preference + * is to put it into the CREATE command, but in some cases that's + * not possible. + */ + if (tbinfo->attgenerated[adnum - 1]) + { + /* + * Column generation expressions cannot be dumped separately, + * because there is no syntax for it. The !shouldPrintColumn + * case below will be tempted to set them to separate if they + * are attached to an inherited column without a local + * definition, but that would be wrong and unnecessary, + * because generation expressions are always inherited, so + * there is no need to set them again in child tables, and + * there is no syntax for it either. By setting separate to + * false here we prevent the "default" from being processed as + * its own dumpable object, and flagInhAttrs() will remove it + * from the table when it detects that it belongs to an + * inherited column. + */ + attrdefs[j].separate = false; + } + else if (tbinfo->relkind == RELKIND_VIEW) { /* - * convalidated is new in 9.2 (actually, it is there in 9.1, - * but it wasn't ever false for check constraints until 9.2). + * Defaults on a VIEW must always be dumped as separate ALTER + * TABLE commands. */ - appendPQExpBuffer(q, "SELECT tableoid, oid, conname, " - "pg_catalog.pg_get_constraintdef(oid) AS consrc, " - "conislocal, convalidated " - "FROM pg_catalog.pg_constraint " - "WHERE conrelid = '%u'::pg_catalog.oid " - " AND contype = 'c' " - "ORDER BY conname", - tbinfo->dobj.catId.oid); + attrdefs[j].separate = true; } - else if (fout->remoteVersion >= 80400) + else if (!shouldPrintColumn(dopt, tbinfo, adnum - 1)) { - /* conislocal is new in 8.4 */ - appendPQExpBuffer(q, "SELECT tableoid, oid, conname, " - "pg_catalog.pg_get_constraintdef(oid) AS consrc, " - "conislocal, true AS convalidated " - "FROM pg_catalog.pg_constraint " - "WHERE conrelid = '%u'::pg_catalog.oid " - " AND contype = 'c' " - "ORDER BY conname", - tbinfo->dobj.catId.oid); + /* column will be suppressed, print default separately */ + attrdefs[j].separate = true; } else { - appendPQExpBuffer(q, "SELECT tableoid, oid, conname, " - "pg_catalog.pg_get_constraintdef(oid) AS consrc, " - "true AS conislocal, true AS convalidated " - "FROM pg_catalog.pg_constraint " - "WHERE conrelid = '%u'::pg_catalog.oid " - " AND contype = 'c' " - "ORDER BY conname", - tbinfo->dobj.catId.oid); + attrdefs[j].separate = false; } - res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); + if (!attrdefs[j].separate) + { + /* + * Mark the default as needing to appear before the table, so + * that any dependencies it has must be emitted before the + * CREATE TABLE. If this is not possible, we'll change to + * "separate" mode while sorting dependencies. + */ + addObjectDependency(&tbinfo->dobj, + attrdefs[j].dobj.dumpId); + } + + tbinfo->attrdefs[adnum - 1] = &attrdefs[j]; + } + + PQclear(res); + } - numConstrs = PQntuples(res); - if (numConstrs != tbinfo->ncheck) + /* + * Get info about table CHECK constraints. This is skipped for a + * data-only dump, as it is only needed for table schemas. + */ + if (!dopt->dataOnly && checkoids->len > 2) + { + ConstraintInfo *constrs; + int numConstrs; + int i_tableoid; + int i_oid; + int i_conrelid; + int i_conname; + int i_consrc; + int i_conislocal; + int i_convalidated; + + pg_log_info("finding table check constraints"); + + resetPQExpBuffer(q); + appendPQExpBufferStr(q, + "SELECT c.tableoid, c.oid, conrelid, conname, " + "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, "); + if (fout->remoteVersion >= 90200) + { + /* + * convalidated is new in 9.2 (actually, it is there in 9.1, but + * it wasn't ever false for check constraints until 9.2). + */ + appendPQExpBufferStr(q, + "conislocal, convalidated "); + } + else if (fout->remoteVersion >= 80400) + { + /* conislocal is new in 8.4 */ + appendPQExpBufferStr(q, + "conislocal, true AS convalidated "); + } + else + { + appendPQExpBufferStr(q, + "true AS conislocal, true AS convalidated "); + } + appendPQExpBuffer(q, + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" + "WHERE contype = 'c' " + "ORDER BY c.conrelid, c.conname", + checkoids->data); + + res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); + + numConstrs = PQntuples(res); + constrs = (ConstraintInfo *) pg_malloc(numConstrs * sizeof(ConstraintInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_conrelid = PQfnumber(res, "conrelid"); + i_conname = PQfnumber(res, "conname"); + i_consrc = PQfnumber(res, "consrc"); + i_conislocal = PQfnumber(res, "conislocal"); + i_convalidated = PQfnumber(res, "convalidated"); + + /* As above, this loop iterates once per table, not once per row */ + curtblindx = -1; + for (int j = 0; j < numConstrs;) + { + Oid conrelid = atooid(PQgetvalue(res, j, i_conrelid)); + TableInfo *tbinfo = NULL; + int numcons; + + /* Count rows for this table */ + for (numcons = 1; numcons < numConstrs - j; numcons++) + if (atooid(PQgetvalue(res, j + numcons, i_conrelid)) != conrelid) + break; + + /* + * Locate the associated TableInfo; we rely on tblinfo[] being in + * OID order. + */ + while (++curtblindx < numTables) + { + tbinfo = &tblinfo[curtblindx]; + if (tbinfo->dobj.catId.oid == conrelid) + break; + } + if (curtblindx >= numTables) + fatal("unrecognized table OID %u", conrelid); + + if (numcons != tbinfo->ncheck) { pg_log_error(ngettext("expected %d check constraint on table \"%s\" but found %d", "expected %d check constraints on table \"%s\" but found %d", tbinfo->ncheck), - tbinfo->ncheck, tbinfo->dobj.name, numConstrs); + tbinfo->ncheck, tbinfo->dobj.name, numcons); pg_log_error("(The system catalogs might be corrupted.)"); exit_nicely(1); } - constrs = (ConstraintInfo *) pg_malloc(numConstrs * sizeof(ConstraintInfo)); - tbinfo->checkexprs = constrs; + tbinfo->checkexprs = constrs + j; - for (int j = 0; j < numConstrs; j++) + for (int c = 0; c < numcons; c++, j++) { - bool validated = PQgetvalue(res, j, 5)[0] == 't'; + bool validated = PQgetvalue(res, j, i_convalidated)[0] == 't'; constrs[j].dobj.objType = DO_CONSTRAINT; - constrs[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, 0)); - constrs[j].dobj.catId.oid = atooid(PQgetvalue(res, j, 1)); + constrs[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid)); + constrs[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid)); AssignDumpId(&constrs[j].dobj); - constrs[j].dobj.name = pg_strdup(PQgetvalue(res, j, 2)); + constrs[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_conname)); constrs[j].dobj.namespace = tbinfo->dobj.namespace; constrs[j].contable = tbinfo; constrs[j].condomain = NULL; constrs[j].contype = 'c'; - constrs[j].condef = pg_strdup(PQgetvalue(res, j, 3)); + constrs[j].condef = pg_strdup(PQgetvalue(res, j, i_consrc)); constrs[j].confrelid = InvalidOid; constrs[j].conindex = 0; constrs[j].condeferrable = false; constrs[j].condeferred = false; - constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't'); + constrs[j].conislocal = (PQgetvalue(res, j, i_conislocal)[0] == 't'); /* * An unvalidated constraint needs to be dumped separately, so @@ -8586,11 +8831,14 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) * constraint must be split out from the table definition. */ } - PQclear(res); } + + PQclear(res); } destroyPQExpBuffer(q); + destroyPQExpBuffer(tbloids); + destroyPQExpBuffer(checkoids); } /* diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 45bae2ffe1..e54c3c04cb 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2425,13 +2425,26 @@ dumpTableData(Archive *fout, const TableDataInfo *tdinfo) /* * Set the TocEntry's dataLength in case we are doing a parallel dump * and want to order dump jobs by table size. We choose to measure - * dataLength in table pages during dump, so no scaling is needed. + * dataLength in table pages (including TOAST pages) during dump, so + * no scaling is needed. + * * However, relpages is declared as "integer" in pg_class, and hence * also in TableInfo, but it's really BlockNumber a/k/a unsigned int. * Cast so that we get the right interpretation of table sizes * exceeding INT_MAX pages. */ te->dataLength = (BlockNumber) tbinfo->relpages; + te->dataLength += (BlockNumber) tbinfo->toastpages; + + /* + * If pgoff_t is only 32 bits wide, the above refinement is useless, + * and instead we'd better worry about integer overflow. Clamp to + * INT_MAX if the correct result exceeds that. + */ + if (sizeof(te->dataLength) == 4 && + (tbinfo->relpages < 0 || tbinfo->toastpages < 0 || + te->dataLength < 0)) + te->dataLength = INT_MAX; } destroyPQExpBuffer(copyBuf); @@ -6086,6 +6099,7 @@ getTables(Archive *fout, int *numTables) int i_relhasindex; int i_relhasrules; int i_relpages; + int i_toastpages; int i_owning_tab; int i_owning_col; int i_reltablespace; @@ -6135,6 +6149,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, " "c.relhasindex, c.relhasrules, c.relpages, " + "tc.relpages AS toastpages, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " "tsp.spcname AS reltablespace, ", @@ -6291,17 +6306,14 @@ getTables(Archive *fout, int *numTables) "LEFT JOIN pg_am am ON (c.relam = am.oid)\n"); /* - * We don't need any data from the TOAST table before 8.2. - * * We purposefully ignore toast OIDs for partitioned tables; the reason is * that versions 10 and 11 have them, but later versions do not, so * emitting them causes the upgrade to fail. */ - if (fout->remoteVersion >= 80200) - appendPQExpBufferStr(query, - "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid" - " AND tc.relkind = " CppAsString2(RELKIND_TOASTVALUE) - " AND c.relkind <> " CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"); + appendPQExpBufferStr(query, + "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 @@ -6352,6 +6364,7 @@ getTables(Archive *fout, int *numTables) i_relhasindex = PQfnumber(res, "relhasindex"); i_relhasrules = PQfnumber(res, "relhasrules"); i_relpages = PQfnumber(res, "relpages"); + i_toastpages = PQfnumber(res, "toastpages"); i_owning_tab = PQfnumber(res, "owning_tab"); i_owning_col = PQfnumber(res, "owning_col"); i_reltablespace = PQfnumber(res, "reltablespace"); @@ -6413,6 +6426,10 @@ getTables(Archive *fout, int *numTables) tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0); tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0); tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages)); + if (PQgetisnull(res, i, i_toastpages)) + tblinfo[i].toastpages = 0; + else + tblinfo[i].toastpages = atoi(PQgetvalue(res, i, i_toastpages)); if (PQgetisnull(res, i, i_owning_tab)) { tblinfo[i].owning_tab = InvalidOid; @@ -10229,7 +10246,7 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) * about is allowing blob dumping to be parallelized, not just * getting a smarter estimate for the single TOC entry.) */ - te->dataLength = MaxBlockNumber; + te->dataLength = INT_MAX; } break; case DO_POLICY: diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 9061812c08..0936ec49bf 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -314,6 +314,7 @@ typedef struct _tableInfo int owning_col; /* attr # of column owning sequence */ bool is_identity_sequence; int relpages; /* table's size in pages (from pg_class) */ + int toastpages; /* toast table's size in pages, if any */ bool interesting; /* true if need to collect more data */ bool dummy_view; /* view's real definition must be postponed */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e54c3c04cb..f33c58a996 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -7332,7 +7332,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) { int i; ConstraintInfo *constrinfo; - PQExpBuffer query; + PQExpBuffer query = createPQExpBuffer(); PGresult *res; int i_tableoid, i_oid, @@ -7340,25 +7340,37 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) i_consrc; int ntups; - query = createPQExpBuffer(); + static bool query_prepared = false; - if (fout->remoteVersion >= 90100) - appendPQExpBuffer(query, "SELECT tableoid, oid, conname, " - "pg_catalog.pg_get_constraintdef(oid) AS consrc, " - "convalidated " - "FROM pg_catalog.pg_constraint " - "WHERE contypid = '%u'::pg_catalog.oid " - "ORDER BY conname", - tyinfo->dobj.catId.oid); + if (!query_prepared) + { + /* Set up query for constraint-specific details */ + appendPQExpBufferStr(query, + "PREPARE getDomainConstraints(pg_catalog.oid) AS\n"); + + if (fout->remoteVersion >= 90100) + appendPQExpBufferStr(query, "SELECT tableoid, oid, conname, " + "pg_catalog.pg_get_constraintdef(oid) AS consrc, " + "convalidated " + "FROM pg_catalog.pg_constraint " + "WHERE contypid = $1 " + "ORDER BY conname"); + else + appendPQExpBufferStr(query, "SELECT tableoid, oid, conname, " + "pg_catalog.pg_get_constraintdef(oid) AS consrc, " + "true as convalidated " + "FROM pg_catalog.pg_constraint " + "WHERE contypid = $1 " + "ORDER BY conname"); - else - appendPQExpBuffer(query, "SELECT tableoid, oid, conname, " - "pg_catalog.pg_get_constraintdef(oid) AS consrc, " - "true as convalidated " - "FROM pg_catalog.pg_constraint " - "WHERE contypid = '%u'::pg_catalog.oid " - "ORDER BY conname", - tyinfo->dobj.catId.oid); + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; + } + + printfPQExpBuffer(query, + "EXECUTE getDomainConstraints('%u')", + tyinfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -10527,18 +10539,33 @@ dumpEnumType(Archive *fout, const TypeInfo *tyinfo) int i_enumlabel; int i_oid; - if (fout->remoteVersion >= 90100) - appendPQExpBuffer(query, "SELECT oid, enumlabel " - "FROM pg_catalog.pg_enum " - "WHERE enumtypid = '%u'" - "ORDER BY enumsortorder", - tyinfo->dobj.catId.oid); - else - appendPQExpBuffer(query, "SELECT oid, enumlabel " - "FROM pg_catalog.pg_enum " - "WHERE enumtypid = '%u'" - "ORDER BY oid", - tyinfo->dobj.catId.oid); + static bool query_prepared = false; + + if (!query_prepared) + { + /* Set up query for enum-specific details */ + appendPQExpBufferStr(query, + "PREPARE dumpEnumType(pg_catalog.oid) AS\n"); + + if (fout->remoteVersion >= 90100) + appendPQExpBufferStr(query, "SELECT oid, enumlabel " + "FROM pg_catalog.pg_enum " + "WHERE enumtypid = $1 " + "ORDER BY enumsortorder"); + else + appendPQExpBufferStr(query, "SELECT oid, enumlabel " + "FROM pg_catalog.pg_enum " + "WHERE enumtypid = $1 " + "ORDER BY oid"); + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpEnumType('%u')", + tyinfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -10657,29 +10684,45 @@ dumpRangeType(Archive *fout, const TypeInfo *tyinfo) char *qualtypname; char *procname; - appendPQExpBuffer(query, - "SELECT "); + static bool query_prepared = false; - if (fout->remoteVersion >= 140000) - appendPQExpBuffer(query, - "pg_catalog.format_type(rngmultitypid, NULL) AS rngmultitype, "); - else - appendPQExpBuffer(query, - "NULL AS rngmultitype, "); + if (!query_prepared) + { + /* Set up query for range-specific details */ + appendPQExpBufferStr(query, + "PREPARE dumpRangeType(pg_catalog.oid) AS\n"); - appendPQExpBuffer(query, - "pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, " - "opc.opcname AS opcname, " - "(SELECT nspname FROM pg_catalog.pg_namespace nsp " - " WHERE nsp.oid = opc.opcnamespace) AS opcnsp, " - "opc.opcdefault, " - "CASE WHEN rngcollation = st.typcollation THEN 0 " - " ELSE rngcollation END AS collation, " - "rngcanonical, rngsubdiff " - "FROM pg_catalog.pg_range r, pg_catalog.pg_type st, " - " pg_catalog.pg_opclass opc " - "WHERE st.oid = rngsubtype AND opc.oid = rngsubopc AND " - "rngtypid = '%u'", + appendPQExpBufferStr(query, + "SELECT "); + + if (fout->remoteVersion >= 140000) + appendPQExpBufferStr(query, + "pg_catalog.format_type(rngmultitypid, NULL) AS rngmultitype, "); + else + appendPQExpBufferStr(query, + "NULL AS rngmultitype, "); + + appendPQExpBufferStr(query, + "pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, " + "opc.opcname AS opcname, " + "(SELECT nspname FROM pg_catalog.pg_namespace nsp " + " WHERE nsp.oid = opc.opcnamespace) AS opcnsp, " + "opc.opcdefault, " + "CASE WHEN rngcollation = st.typcollation THEN 0 " + " ELSE rngcollation END AS collation, " + "rngcanonical, rngsubdiff " + "FROM pg_catalog.pg_range r, pg_catalog.pg_type st, " + " pg_catalog.pg_opclass opc " + "WHERE st.oid = rngsubtype AND opc.oid = rngsubopc AND " + "rngtypid = $1"); + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpRangeType('%u')", tyinfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -10887,55 +10930,70 @@ dumpBaseType(Archive *fout, const TypeInfo *tyinfo) char *typdefault; bool typdefault_is_literal = false; - /* Fetch type-specific details */ - appendPQExpBufferStr(query, "SELECT typlen, " - "typinput, typoutput, typreceive, typsend, " - "typreceive::pg_catalog.oid AS typreceiveoid, " - "typsend::pg_catalog.oid AS typsendoid, " - "typanalyze, " - "typanalyze::pg_catalog.oid AS typanalyzeoid, " - "typdelim, typbyval, typalign, typstorage, "); + static bool query_prepared = false; - if (fout->remoteVersion >= 80300) - appendPQExpBufferStr(query, - "typmodin, typmodout, " - "typmodin::pg_catalog.oid AS typmodinoid, " - "typmodout::pg_catalog.oid AS typmodoutoid, "); - else + if (!query_prepared) + { + /* Set up query for type-specific details */ appendPQExpBufferStr(query, - "'-' AS typmodin, '-' AS typmodout, " - "0 AS typmodinoid, 0 AS typmodoutoid, "); + "PREPARE dumpBaseType(pg_catalog.oid) AS\n"); - if (fout->remoteVersion >= 80400) - appendPQExpBufferStr(query, - "typcategory, typispreferred, "); - else - appendPQExpBufferStr(query, - "'U' AS typcategory, false AS typispreferred, "); + appendPQExpBufferStr(query, "SELECT typlen, " + "typinput, typoutput, typreceive, typsend, " + "typreceive::pg_catalog.oid AS typreceiveoid, " + "typsend::pg_catalog.oid AS typsendoid, " + "typanalyze, " + "typanalyze::pg_catalog.oid AS typanalyzeoid, " + "typdelim, typbyval, typalign, typstorage, "); - if (fout->remoteVersion >= 90100) - appendPQExpBufferStr(query, "(typcollation <> 0) AS typcollatable, "); - else - appendPQExpBufferStr(query, "false AS typcollatable, "); + if (fout->remoteVersion >= 80300) + appendPQExpBufferStr(query, + "typmodin, typmodout, " + "typmodin::pg_catalog.oid AS typmodinoid, " + "typmodout::pg_catalog.oid AS typmodoutoid, "); + else + appendPQExpBufferStr(query, + "'-' AS typmodin, '-' AS typmodout, " + "0 AS typmodinoid, 0 AS typmodoutoid, "); - if (fout->remoteVersion >= 140000) - appendPQExpBufferStr(query, - "typsubscript, " - "typsubscript::pg_catalog.oid AS typsubscriptoid, "); - else - appendPQExpBufferStr(query, - "'-' AS typsubscript, 0 AS typsubscriptoid, "); + if (fout->remoteVersion >= 80400) + appendPQExpBufferStr(query, + "typcategory, typispreferred, "); + else + appendPQExpBufferStr(query, + "'U' AS typcategory, false AS typispreferred, "); - /* Before 8.4, pg_get_expr does not allow 0 for its second arg */ - if (fout->remoteVersion >= 80400) - appendPQExpBufferStr(query, - "pg_catalog.pg_get_expr(typdefaultbin, 0) AS typdefaultbin, typdefault "); - else - appendPQExpBufferStr(query, - "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin,typdefault "); + if (fout->remoteVersion >= 90100) + appendPQExpBufferStr(query, "(typcollation <> 0) AS typcollatable, "); + else + appendPQExpBufferStr(query, "false AS typcollatable, "); - appendPQExpBuffer(query, "FROM pg_catalog.pg_type " - "WHERE oid = '%u'::pg_catalog.oid", + if (fout->remoteVersion >= 140000) + appendPQExpBufferStr(query, + "typsubscript, " + "typsubscript::pg_catalog.oid AS typsubscriptoid, "); + else + appendPQExpBufferStr(query, + "'-' AS typsubscript, 0 AS typsubscriptoid, "); + + /* Before 8.4, pg_get_expr does not allow 0 for its second arg */ + if (fout->remoteVersion >= 80400) + appendPQExpBufferStr(query, + "pg_catalog.pg_get_expr(typdefaultbin, 0) AS typdefaultbin, typdefault "); + else + appendPQExpBufferStr(query, + "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin,typdefault "); + + appendPQExpBufferStr(query, "FROM pg_catalog.pg_type " + "WHERE oid = $1"); + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpBaseType('%u')", tyinfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -11130,32 +11188,46 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) Oid typcollation; bool typdefault_is_literal = false; - /* Fetch domain specific details */ - if (fout->remoteVersion >= 90100) - { - /* typcollation is new in 9.1 */ - appendPQExpBuffer(query, "SELECT t.typnotnull, " - "pg_catalog.format_type(t.typbasetype, t.typtypmod) AS typdefn, " - "pg_catalog.pg_get_expr(t.typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin," - "t.typdefault, " - "CASE WHEN t.typcollation <> u.typcollation " - "THEN t.typcollation ELSE 0 END AS typcollation " - "FROM pg_catalog.pg_type t " - "LEFT JOIN pg_catalog.pg_type u ON (t.typbasetype = u.oid) " - "WHERE t.oid = '%u'::pg_catalog.oid", - tyinfo->dobj.catId.oid); - } - else + static bool query_prepared = false; + + if (!query_prepared) { - appendPQExpBuffer(query, "SELECT typnotnull, " - "pg_catalog.format_type(typbasetype, typtypmod) AS typdefn, " - "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin," - "typdefault, 0 AS typcollation " - "FROM pg_catalog.pg_type " - "WHERE oid = '%u'::pg_catalog.oid", - tyinfo->dobj.catId.oid); + /* Set up query for domain-specific details */ + appendPQExpBufferStr(query, + "PREPARE dumpDomain(pg_catalog.oid) AS\n"); + + if (fout->remoteVersion >= 90100) + { + /* typcollation is new in 9.1 */ + appendPQExpBufferStr(query, "SELECT t.typnotnull, " + "pg_catalog.format_type(t.typbasetype, t.typtypmod) AS typdefn, " + "pg_catalog.pg_get_expr(t.typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AStypdefaultbin, " + "t.typdefault, " + "CASE WHEN t.typcollation <> u.typcollation " + "THEN t.typcollation ELSE 0 END AS typcollation " + "FROM pg_catalog.pg_type t " + "LEFT JOIN pg_catalog.pg_type u ON (t.typbasetype = u.oid) " + "WHERE t.oid = $1"); + } + else + { + appendPQExpBufferStr(query, "SELECT typnotnull, " + "pg_catalog.format_type(typbasetype, typtypmod) AS typdefn, " + "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin," + "typdefault, 0 AS typcollation " + "FROM pg_catalog.pg_type " + "WHERE oid = $1"); + } + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; } + printfPQExpBuffer(query, + "EXECUTE dumpDomain('%u')", + tyinfo->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); typnotnull = PQgetvalue(res, 0, PQfnumber(res, "typnotnull")); @@ -11308,45 +11380,59 @@ dumpCompositeType(Archive *fout, const TypeInfo *tyinfo) int i; int actual_atts; - /* Fetch type specific details */ - if (fout->remoteVersion >= 90100) - { - /* - * attcollation is new in 9.1. Since we only want to dump COLLATE - * clauses for attributes whose collation is different from their - * type's default, we use a CASE here to suppress uninteresting - * attcollations cheaply. atttypid will be 0 for dropped columns; - * collation does not matter for those. - */ - appendPQExpBuffer(query, "SELECT a.attname, " - "pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttypdefn, " - "a.attlen, a.attalign, a.attisdropped, " - "CASE WHEN a.attcollation <> at.typcollation " - "THEN a.attcollation ELSE 0 END AS attcollation " - "FROM pg_catalog.pg_type ct " - "JOIN pg_catalog.pg_attribute a ON a.attrelid = ct.typrelid " - "LEFT JOIN pg_catalog.pg_type at ON at.oid = a.atttypid " - "WHERE ct.oid = '%u'::pg_catalog.oid " - "ORDER BY a.attnum ", - tyinfo->dobj.catId.oid); - } - else + static bool query_prepared = false; + + if (!query_prepared) { - /* - * Since ALTER TYPE could not drop columns until 9.1, attisdropped - * should always be false. - */ - appendPQExpBuffer(query, "SELECT a.attname, " - "pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttypdefn, " - "a.attlen, a.attalign, a.attisdropped, " - "0 AS attcollation " - "FROM pg_catalog.pg_type ct, pg_catalog.pg_attribute a " - "WHERE ct.oid = '%u'::pg_catalog.oid " - "AND a.attrelid = ct.typrelid " - "ORDER BY a.attnum ", - tyinfo->dobj.catId.oid); + /* Set up query for type-specific details */ + appendPQExpBufferStr(query, + "PREPARE dumpCompositeType(pg_catalog.oid) AS\n"); + + if (fout->remoteVersion >= 90100) + { + /* + * attcollation is new in 9.1. Since we only want to dump COLLATE + * clauses for attributes whose collation is different from their + * type's default, we use a CASE here to suppress uninteresting + * attcollations cheaply. atttypid will be 0 for dropped columns; + * collation does not matter for those. + */ + appendPQExpBufferStr(query, "SELECT a.attname, " + "pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttypdefn, " + "a.attlen, a.attalign, a.attisdropped, " + "CASE WHEN a.attcollation <> at.typcollation " + "THEN a.attcollation ELSE 0 END AS attcollation " + "FROM pg_catalog.pg_type ct " + "JOIN pg_catalog.pg_attribute a ON a.attrelid = ct.typrelid " + "LEFT JOIN pg_catalog.pg_type at ON at.oid = a.atttypid " + "WHERE ct.oid = $1 " + "ORDER BY a.attnum"); + } + else + { + /* + * Since ALTER TYPE could not drop columns until 9.1, attisdropped + * should always be false. + */ + appendPQExpBufferStr(query, "SELECT a.attname, " + "pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttypdefn, " + "a.attlen, a.attalign, a.attisdropped, " + "0 AS attcollation " + "FROM pg_catalog.pg_type ct, pg_catalog.pg_attribute a " + "WHERE ct.oid = $1 " + "AND a.attrelid = ct.typrelid " + "ORDER BY a.attnum"); + } + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; } + printfPQExpBuffer(query, + "EXECUTE dumpCompositeType('%u')", + tyinfo->dobj.catId.oid); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); @@ -11956,6 +12042,8 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) const char *keyword; int i; + static bool query_prepared = false; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -11965,96 +12053,109 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) delqry = createPQExpBuffer(); asPart = createPQExpBuffer(); - /* Fetch function-specific details */ - appendPQExpBufferStr(query, - "SELECT\n" - "proretset,\n" - "prosrc,\n" - "probin,\n" - "provolatile,\n" - "proisstrict,\n" - "prosecdef,\n" - "lanname,\n"); - - if (fout->remoteVersion >= 80300) - appendPQExpBufferStr(query, - "proconfig,\n" - "procost,\n" - "prorows,\n"); - else - appendPQExpBufferStr(query, - "null AS proconfig,\n" - "0 AS procost,\n" - "0 AS prorows,\n"); - - if (fout->remoteVersion >= 80400) + if (!query_prepared) { - /* - * In 8.4 and up we rely on pg_get_function_arguments and - * pg_get_function_result instead of examining proallargtypes etc. - */ + /* Set up query for function-specific details */ appendPQExpBufferStr(query, - "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" - "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n" - "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n"); - } - else if (fout->remoteVersion >= 80100) - appendPQExpBufferStr(query, - "proallargtypes,\n" - "proargmodes,\n" - "proargnames,\n"); - else - appendPQExpBufferStr(query, - "null AS proallargtypes,\n" - "null AS proargmodes,\n" - "proargnames,\n"); + "PREPARE dumpFunc(pg_catalog.oid) AS\n"); - if (fout->remoteVersion >= 90200) - appendPQExpBufferStr(query, - "proleakproof,\n"); - else appendPQExpBufferStr(query, - "false AS proleakproof,\n"); + "SELECT\n" + "proretset,\n" + "prosrc,\n" + "probin,\n" + "provolatile,\n" + "proisstrict,\n" + "prosecdef,\n" + "lanname,\n"); + + if (fout->remoteVersion >= 80300) + appendPQExpBufferStr(query, + "proconfig,\n" + "procost,\n" + "prorows,\n"); + else + appendPQExpBufferStr(query, + "null AS proconfig,\n" + "0 AS procost,\n" + "0 AS prorows,\n"); - if (fout->remoteVersion >= 90500) - appendPQExpBufferStr(query, - "array_to_string(protrftypes, ' ') AS protrftypes,\n"); + if (fout->remoteVersion >= 80400) + { + /* + * In 8.4 and up we rely on pg_get_function_arguments and + * pg_get_function_result instead of examining proallargtypes etc. + */ + appendPQExpBufferStr(query, + "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" + "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n" + "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n"); + } + else if (fout->remoteVersion >= 80100) + appendPQExpBufferStr(query, + "proallargtypes,\n" + "proargmodes,\n" + "proargnames,\n"); + else + appendPQExpBufferStr(query, + "null AS proallargtypes,\n" + "null AS proargmodes,\n" + "proargnames,\n"); - if (fout->remoteVersion >= 90600) - appendPQExpBufferStr(query, - "proparallel,\n"); - else - appendPQExpBufferStr(query, - "'u' AS proparallel,\n"); + if (fout->remoteVersion >= 90200) + appendPQExpBufferStr(query, + "proleakproof,\n"); + else + appendPQExpBufferStr(query, + "false AS proleakproof,\n"); - if (fout->remoteVersion >= 110000) - appendPQExpBufferStr(query, - "prokind,\n"); - else if (fout->remoteVersion >= 80400) - appendPQExpBufferStr(query, - "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind,\n"); - else - appendPQExpBufferStr(query, - "'f' AS prokind,\n"); + if (fout->remoteVersion >= 90500) + appendPQExpBufferStr(query, + "array_to_string(protrftypes, ' ') AS protrftypes,\n"); - if (fout->remoteVersion >= 120000) - appendPQExpBufferStr(query, - "prosupport,\n"); - else - appendPQExpBufferStr(query, - "'-' AS prosupport,\n"); + if (fout->remoteVersion >= 90600) + appendPQExpBufferStr(query, + "proparallel,\n"); + else + appendPQExpBufferStr(query, + "'u' AS proparallel,\n"); + + if (fout->remoteVersion >= 110000) + appendPQExpBufferStr(query, + "prokind,\n"); + else if (fout->remoteVersion >= 80400) + appendPQExpBufferStr(query, + "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind,\n"); + else + appendPQExpBufferStr(query, + "'f' AS prokind,\n"); + + if (fout->remoteVersion >= 120000) + appendPQExpBufferStr(query, + "prosupport,\n"); + else + appendPQExpBufferStr(query, + "'-' AS prosupport,\n"); + + if (fout->remoteVersion >= 140000) + appendPQExpBufferStr(query, + "pg_get_function_sqlbody(p.oid) AS prosqlbody\n"); + else + appendPQExpBufferStr(query, + "NULL AS prosqlbody\n"); - if (fout->remoteVersion >= 140000) - appendPQExpBufferStr(query, - "pg_get_function_sqlbody(p.oid) AS prosqlbody\n"); - else appendPQExpBufferStr(query, - "NULL AS prosqlbody\n"); + "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n" + "WHERE p.oid = $1 " + "AND l.oid = p.prolang"); - appendPQExpBuffer(query, - "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n" - "WHERE p.oid = '%u'::pg_catalog.oid " - "AND l.oid = p.prolang", + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpFunc('%u')", finfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -12704,6 +12805,8 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo) char *oprregproc; char *oprref; + static bool query_prepared = false; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -12721,38 +12824,51 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo) oprid = createPQExpBuffer(); details = createPQExpBuffer(); - if (fout->remoteVersion >= 80300) - { - appendPQExpBuffer(query, "SELECT oprkind, " - "oprcode::pg_catalog.regprocedure, " - "oprleft::pg_catalog.regtype, " - "oprright::pg_catalog.regtype, " - "oprcom, " - "oprnegate, " - "oprrest::pg_catalog.regprocedure, " - "oprjoin::pg_catalog.regprocedure, " - "oprcanmerge, oprcanhash " - "FROM pg_catalog.pg_operator " - "WHERE oid = '%u'::pg_catalog.oid", - oprinfo->dobj.catId.oid); - } - else + if (!query_prepared) { - appendPQExpBuffer(query, "SELECT oprkind, " - "oprcode::pg_catalog.regprocedure, " - "oprleft::pg_catalog.regtype, " - "oprright::pg_catalog.regtype, " - "oprcom, " - "oprnegate, " - "oprrest::pg_catalog.regprocedure, " - "oprjoin::pg_catalog.regprocedure, " - "(oprlsortop != 0) AS oprcanmerge, " - "oprcanhash " - "FROM pg_catalog.pg_operator " - "WHERE oid = '%u'::pg_catalog.oid", - oprinfo->dobj.catId.oid); + /* Set up query for operator-specific details */ + appendPQExpBufferStr(query, + "PREPARE dumpOpr(pg_catalog.oid) AS\n"); + + if (fout->remoteVersion >= 80300) + { + appendPQExpBufferStr(query, "SELECT oprkind, " + "oprcode::pg_catalog.regprocedure, " + "oprleft::pg_catalog.regtype, " + "oprright::pg_catalog.regtype, " + "oprcom, " + "oprnegate, " + "oprrest::pg_catalog.regprocedure, " + "oprjoin::pg_catalog.regprocedure, " + "oprcanmerge, oprcanhash " + "FROM pg_catalog.pg_operator " + "WHERE oid = $1"); + } + else + { + appendPQExpBufferStr(query, "SELECT oprkind, " + "oprcode::pg_catalog.regprocedure, " + "oprleft::pg_catalog.regtype, " + "oprright::pg_catalog.regtype, " + "oprcom, " + "oprnegate, " + "oprrest::pg_catalog.regprocedure, " + "oprjoin::pg_catalog.regprocedure, " + "(oprlsortop != 0) AS oprcanmerge, " + "oprcanhash " + "FROM pg_catalog.pg_operator " + "WHERE oid = $1"); + } + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; } + printfPQExpBuffer(query, + "EXECUTE dumpOpr('%u')", + oprinfo->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); i_oprkind = PQfnumber(res, "oprkind"); @@ -14009,6 +14125,8 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) const char *proparallel; char defaultfinalmodify; + static bool query_prepared = false; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -14018,77 +14136,90 @@ dumpAgg(Archive *fout, const AggInfo *agginfo) delq = createPQExpBuffer(); details = createPQExpBuffer(); - /* Get aggregate-specific details */ - appendPQExpBufferStr(query, - "SELECT\n" - "aggtransfn,\n" - "aggfinalfn,\n" - "aggtranstype::pg_catalog.regtype,\n" - "agginitval,\n"); - - if (fout->remoteVersion >= 80100) - appendPQExpBufferStr(query, - "aggsortop,\n"); - else + if (!query_prepared) + { + /* Set up query for aggregate-specific details */ appendPQExpBufferStr(query, - "0 AS aggsortop,\n"); + "PREPARE dumpAgg(pg_catalog.oid) AS\n"); - if (fout->remoteVersion >= 80400) appendPQExpBufferStr(query, - "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" - "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"); + "SELECT " + "aggtransfn,\n" + "aggfinalfn,\n" + "aggtranstype::pg_catalog.regtype,\n" + "agginitval,\n"); - if (fout->remoteVersion >= 90400) - appendPQExpBufferStr(query, - "aggkind,\n" - "aggmtransfn,\n" - "aggminvtransfn,\n" - "aggmfinalfn,\n" - "aggmtranstype::pg_catalog.regtype,\n" - "aggfinalextra,\n" - "aggmfinalextra,\n" - "aggtransspace,\n" - "aggmtransspace,\n" - "aggminitval,\n"); - else - appendPQExpBufferStr(query, - "'n' AS aggkind,\n" - "'-' AS aggmtransfn,\n" - "'-' AS aggminvtransfn,\n" - "'-' AS aggmfinalfn,\n" - "0 AS aggmtranstype,\n" - "false AS aggfinalextra,\n" - "false AS aggmfinalextra,\n" - "0 AS aggtransspace,\n" - "0 AS aggmtransspace,\n" - "NULL AS aggminitval,\n"); + if (fout->remoteVersion >= 80100) + appendPQExpBufferStr(query, + "aggsortop,\n"); + else + appendPQExpBufferStr(query, + "0 AS aggsortop,\n"); - if (fout->remoteVersion >= 90600) - appendPQExpBufferStr(query, - "aggcombinefn,\n" - "aggserialfn,\n" - "aggdeserialfn,\n" - "proparallel,\n"); - else - appendPQExpBufferStr(query, - "'-' AS aggcombinefn,\n" - "'-' AS aggserialfn,\n" - "'-' AS aggdeserialfn,\n" - "'u' AS proparallel,\n"); + if (fout->remoteVersion >= 80400) + appendPQExpBufferStr(query, + "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n" + "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"); + + if (fout->remoteVersion >= 90400) + appendPQExpBufferStr(query, + "aggkind,\n" + "aggmtransfn,\n" + "aggminvtransfn,\n" + "aggmfinalfn,\n" + "aggmtranstype::pg_catalog.regtype,\n" + "aggfinalextra,\n" + "aggmfinalextra,\n" + "aggtransspace,\n" + "aggmtransspace,\n" + "aggminitval,\n"); + else + appendPQExpBufferStr(query, + "'n' AS aggkind,\n" + "'-' AS aggmtransfn,\n" + "'-' AS aggminvtransfn,\n" + "'-' AS aggmfinalfn,\n" + "0 AS aggmtranstype,\n" + "false AS aggfinalextra,\n" + "false AS aggmfinalextra,\n" + "0 AS aggtransspace,\n" + "0 AS aggmtransspace,\n" + "NULL AS aggminitval,\n"); + + if (fout->remoteVersion >= 90600) + appendPQExpBufferStr(query, + "aggcombinefn,\n" + "aggserialfn,\n" + "aggdeserialfn,\n" + "proparallel,\n"); + else + appendPQExpBufferStr(query, + "'-' AS aggcombinefn,\n" + "'-' AS aggserialfn,\n" + "'-' AS aggdeserialfn,\n" + "'u' AS proparallel,\n"); + + if (fout->remoteVersion >= 110000) + appendPQExpBufferStr(query, + "aggfinalmodify,\n" + "aggmfinalmodify\n"); + else + appendPQExpBufferStr(query, + "'0' AS aggfinalmodify,\n" + "'0' AS aggmfinalmodify\n"); - if (fout->remoteVersion >= 110000) - appendPQExpBufferStr(query, - "aggfinalmodify,\n" - "aggmfinalmodify\n"); - else appendPQExpBufferStr(query, - "'0' AS aggfinalmodify,\n" - "'0' AS aggmfinalmodify\n"); + "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " + "WHERE a.aggfnoid = p.oid " + "AND p.oid = $1"); - appendPQExpBuffer(query, - "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " - "WHERE a.aggfnoid = p.oid " - "AND p.oid = '%u'::pg_catalog.oid", + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpAgg('%u')", agginfo->aggfn.dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -15502,46 +15633,61 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) PGresult *res; int i; - if (fout->remoteVersion >= 90600) - { - /* - * 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, " - "at.attacl, " - "'{}' AS acldefault, " - "pip.privtype, pip.initprivs " - "FROM pg_catalog.pg_attribute at " - "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 (at.attacl IS NOT NULL OR pip.initprivs IS NOT NULL) " - "ORDER BY at.attnum", - tbinfo->dobj.catId.oid); - } - else + static bool query_prepared = false; + + if (!query_prepared) { - appendPQExpBuffer(query, - "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 " - "ORDER BY attnum", - tbinfo->dobj.catId.oid); + /* Set up query for column ACLs */ + appendPQExpBufferStr(query, + "PREPARE getColumnACLs(pg_catalog.oid) AS\n"); + + if (fout->remoteVersion >= 90600) + { + /* + * 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. + */ + appendPQExpBufferStr(query, + "SELECT at.attname, " + "at.attacl, " + "'{}' AS acldefault, " + "pip.privtype, pip.initprivs " + "FROM pg_catalog.pg_attribute at " + "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 = $1 AND " + "NOT at.attisdropped " + "AND (at.attacl IS NOT NULL OR pip.initprivs IS NOT NULL) " + "ORDER BY at.attnum"); + } + else + { + appendPQExpBufferStr(query, + "SELECT attname, attacl, '{}' AS acldefault, " + "NULL AS privtype, NULL AS initprivs " + "FROM pg_catalog.pg_attribute " + "WHERE attrelid = $1 AND NOT attisdropped " + "AND attacl IS NOT NULL " + "ORDER BY attnum"); + } + + ExecuteSqlStatement(fout, query->data); + + query_prepared = true; } + printfPQExpBuffer(query, + "EXECUTE getColumnACLs('%u')", + tbinfo->dobj.catId.oid); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); for (i = 0; i < PQntuples(res); i++) @@ -16472,6 +16618,8 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) PGresult *res; char *partbound; + static bool query_prepared = false; + /* Do nothing in data-only dump */ if (dopt->dataOnly) return; @@ -16481,12 +16629,26 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo) q = createPQExpBuffer(); - /* Fetch the partition's partbound */ - appendPQExpBuffer(q, - "SELECT pg_get_expr(c.relpartbound, c.oid) " - "FROM pg_class c " - "WHERE c.oid = '%u'", + if (!query_prepared) + { + /* Set up query for partbound details */ + appendPQExpBufferStr(q, + "PREPARE dumpTableAttach(pg_catalog.oid) AS\n"); + + appendPQExpBufferStr(q, + "SELECT pg_get_expr(c.relpartbound, c.oid) " + "FROM pg_class c " + "WHERE c.oid = $1"); + + ExecuteSqlStatement(fout, q->data); + + query_prepared = true; + } + + printfPQExpBuffer(q, + "EXECUTE dumpTableAttach('%u')", attachinfo->partitionTbl->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, q->data); partbound = PQgetvalue(res, 0, 0);
pgsql-hackers by date: