Re: Assorted improvements in pg_dump - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Assorted improvements in pg_dump |
Date | |
Msg-id | 177444.1635287465@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Assorted improvements in pg_dump (Andres Freund <andres@anarazel.de>) |
Responses |
Re: Assorted improvements in pg_dump
|
List | pgsql-hackers |
Andres Freund <andres@anarazel.de> writes: > I guess we could move the prepared-statement handling into a query execution > helper. That could then use a hashtable or something similar to check if a > certain prepared statement already exists. That'd then centrally be extensible > to deal with multiple connects etc. That seems like more mechanism than is warranted. I tried it with a simple array of booleans, and that seems like not too much of a mess; see revised 0006 attached. (0001-0005 are the same as before; including them just to satisfy the cfbot.) regards, tom lane 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_backup.h b/src/bin/pg_dump/pg_backup.h index 6af10a85a2..d3aac0dbdf 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -58,6 +58,23 @@ typedef enum _teSection SECTION_POST_DATA /* stuff to be processed after data */ } teSection; +/* We need one enum entry per prepared query in pg_dump */ +enum _dumpPreparedQueries +{ + PREPQUERY_DUMPAGG, + PREPQUERY_DUMPBASETYPE, + PREPQUERY_DUMPCOMPOSITETYPE, + PREPQUERY_DUMPDOMAIN, + PREPQUERY_DUMPENUMTYPE, + PREPQUERY_DUMPFUNC, + PREPQUERY_DUMPOPR, + PREPQUERY_DUMPRANGETYPE, + PREPQUERY_DUMPTABLEATTACH, + PREPQUERY_GETCOLUMNACLS, + PREPQUERY_GETDOMAINCONSTRAINTS, + NUM_PREP_QUERIES /* must be last */ +}; + /* Parameters needed by ConnectDatabase; same for dump and restore */ typedef struct _connParams { @@ -214,6 +231,9 @@ typedef struct Archive bool exit_on_error; /* whether to exit on SQL errors... */ int n_errors; /* number of errors (if no die) */ + /* prepared-query status */ + bool *is_prepared; /* indexed by enum _dumpPreparedQueries */ + /* The rest is private */ } Archive; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e54c3c04cb..212203a32e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1208,6 +1208,12 @@ setup_connection(Archive *AH, const char *dumpencoding, ExecuteSqlStatement(AH, "SET row_security = off"); } + /* + * Initialize prepared-query state to "nothing prepared". We do this here + * so that a parallel dump worker will have its own state. + */ + AH->is_prepared = (bool *) pg_malloc0(NUM_PREP_QUERIES * sizeof(bool)); + /* * Start transaction-snapshot mode transaction to dump consistent data. */ @@ -7332,7 +7338,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) { int i; ConstraintInfo *constrinfo; - PQExpBuffer query; + PQExpBuffer query = createPQExpBuffer(); PGresult *res; int i_tableoid, i_oid, @@ -7340,25 +7346,35 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) i_consrc; int ntups; - query = createPQExpBuffer(); + if (!fout->is_prepared[PREPQUERY_GETDOMAINCONSTRAINTS]) + { + /* 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"); - 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); + ExecuteSqlStatement(fout, query->data); - 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); + fout->is_prepared[PREPQUERY_GETDOMAINCONSTRAINTS] = true; + } + + printfPQExpBuffer(query, + "EXECUTE getDomainConstraints('%u')", + tyinfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -10527,18 +10543,31 @@ 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); + if (!fout->is_prepared[PREPQUERY_DUMPENUMTYPE]) + { + /* 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); + + fout->is_prepared[PREPQUERY_DUMPENUMTYPE] = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpEnumType('%u')", + tyinfo->dobj.catId.oid); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -10657,29 +10686,43 @@ dumpRangeType(Archive *fout, const TypeInfo *tyinfo) char *qualtypname; char *procname; - appendPQExpBuffer(query, - "SELECT "); + if (!fout->is_prepared[PREPQUERY_DUMPRANGETYPE]) + { + /* Set up query for range-specific details */ + appendPQExpBufferStr(query, + "PREPARE dumpRangeType(pg_catalog.oid) AS\n"); - if (fout->remoteVersion >= 140000) - appendPQExpBuffer(query, - "pg_catalog.format_type(rngmultitypid, NULL) AS rngmultitype, "); - else - appendPQExpBuffer(query, - "NULL AS rngmultitype, "); + appendPQExpBufferStr(query, + "SELECT "); - 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'", + 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); + + fout->is_prepared[PREPQUERY_DUMPRANGETYPE] = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpRangeType('%u')", tyinfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -10887,55 +10930,68 @@ 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, "); - - if (fout->remoteVersion >= 80300) - appendPQExpBufferStr(query, - "typmodin, typmodout, " - "typmodin::pg_catalog.oid AS typmodinoid, " - "typmodout::pg_catalog.oid AS typmodoutoid, "); - else + if (!fout->is_prepared[PREPQUERY_DUMPBASETYPE]) + { + /* 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); + + fout->is_prepared[PREPQUERY_DUMPBASETYPE] = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpBaseType('%u')", tyinfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -11130,32 +11186,44 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) Oid typcollation; bool typdefault_is_literal = false; - /* Fetch domain specific details */ - if (fout->remoteVersion >= 90100) + if (!fout->is_prepared[PREPQUERY_DUMPDOMAIN]) { - /* 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 - { - 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); + + fout->is_prepared[PREPQUERY_DUMPDOMAIN] = true; } + printfPQExpBuffer(query, + "EXECUTE dumpDomain('%u')", + tyinfo->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); typnotnull = PQgetvalue(res, 0, PQfnumber(res, "typnotnull")); @@ -11308,45 +11376,57 @@ 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 + if (!fout->is_prepared[PREPQUERY_DUMPCOMPOSITETYPE]) { - /* - * 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); + + fout->is_prepared[PREPQUERY_DUMPCOMPOSITETYPE] = true; } + printfPQExpBuffer(query, + "EXECUTE dumpCompositeType('%u')", + tyinfo->dobj.catId.oid); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); ntups = PQntuples(res); @@ -11965,96 +12045,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 (!fout->is_prepared[PREPQUERY_DUMPFUNC]) { - /* - * 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); + + fout->is_prepared[PREPQUERY_DUMPFUNC] = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpFunc('%u')", finfo->dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -12721,38 +12814,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 (!fout->is_prepared[PREPQUERY_DUMPOPR]) { - 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); + + fout->is_prepared[PREPQUERY_DUMPOPR] = true; } + printfPQExpBuffer(query, + "EXECUTE dumpOpr('%u')", + oprinfo->dobj.catId.oid); + res = ExecuteSqlQueryForSingleRow(fout, query->data); i_oprkind = PQfnumber(res, "oprkind"); @@ -14018,77 +14124,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 (!fout->is_prepared[PREPQUERY_DUMPAGG]) + { + /* 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); + + fout->is_prepared[PREPQUERY_DUMPAGG] = true; + } + + printfPQExpBuffer(query, + "EXECUTE dumpAgg('%u')", agginfo->aggfn.dobj.catId.oid); res = ExecuteSqlQueryForSingleRow(fout, query->data); @@ -15502,46 +15621,59 @@ dumpTable(Archive *fout, const TableInfo *tbinfo) PGresult *res; int i; - if (fout->remoteVersion >= 90600) + if (!fout->is_prepared[PREPQUERY_GETCOLUMNACLS]) { - /* - * 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 - { - 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); + + fout->is_prepared[PREPQUERY_GETCOLUMNACLS] = 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++) @@ -16481,12 +16613,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 (!fout->is_prepared[PREPQUERY_DUMPTABLEATTACH]) + { + /* 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); + + fout->is_prepared[PREPQUERY_DUMPTABLEATTACH] = 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: