raven/base/pglogical/pglogical_dump.patch
2024-02-21 13:47:54 +06:00

32169 lines
917 KiB
Diff

Description: <short summary of the patch>
TODO: Put a short summary on the line above and replace this paragraph
with a longer explanation of this change. Complete the meta-information
with other relevant fields (see below for details). To make it easier, the
information below has been extracted from the changelog. Adjust it or drop
it.
.
pglogical (2.2.2-1) UNRELEASED; urgency=medium
.
[ Debian PostgreSQL Maintainers ]
* New upstream minor release.
.
[ Michael Banck ]
* debian/patches/adapt_tap_tests.patch: Updated.
* debian/patches/v94_tap_support.patch: Likewise.
* debian/patches/test_increase_timeouts.patch: Likewise.
Author: Debian PostgreSQL Maintainers <team+postgresql@tracker.debian.org>
---
The information above should follow the Patch Tagging Guidelines, please
checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
are templates for supplementary fields that you might want to add:
Origin: <vendor|upstream|other>, <url of original patch>
Bug: <url in upstream bugtracker>
Bug-Debian: https://bugs.debian.org/<bugnumber>
Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
Forwarded: <no|not-needed|url proving that it has been forwarded>
Reviewed-By: <name and email of someone who approved the patch>
Last-Update: 2019-08-05
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/Makefile
@@ -0,0 +1,16 @@
+PGFILEDESC = "pglogical_dump - pg_dump 9.4 with --snapshot support"
+PGAPPICON = win32
+
+PROGRAM = pglogical_dump
+OBJS = pg_dump.o common.o pg_dump_sort.o \
+ pg_backup_archiver.o pg_backup_db.o pg_backup_custom.o \
+ pg_backup_null.o pg_backup_tar.o pg_backup_directory.o \
+ pg_backup_utils.o parallel.o compress_io.o dumputils.o \
+ keywords.o kwlookup.o tar.o $(WIN32RES)
+
+PG_CPPFLAGS = -I$(libpq_srcdir)
+PG_LIBS = $(libpq_pgport)
+
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/common.c
@@ -0,0 +1,913 @@
+/*-------------------------------------------------------------------------
+ *
+ * common.c
+ * Catalog routines used by pg_dump; long ago these were shared
+ * by another dump tool, but not anymore.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "pg_backup_archiver.h"
+#include "pg_backup_utils.h"
+
+#include <ctype.h>
+
+#include "catalog/pg_class.h"
+
+
+/*
+ * Variables for mapping DumpId to DumpableObject
+ */
+static DumpableObject **dumpIdMap = NULL;
+static int allocedDumpIds = 0;
+static DumpId lastDumpId = 0;
+
+/*
+ * Variables for mapping CatalogId to DumpableObject
+ */
+static bool catalogIdMapValid = false;
+static DumpableObject **catalogIdMap = NULL;
+static int numCatalogIds = 0;
+
+/*
+ * These variables are static to avoid the notational cruft of having to pass
+ * them into findTableByOid() and friends. For each of these arrays, we
+ * build a sorted-by-OID index array immediately after it's built, and then
+ * we use binary search in findTableByOid() and friends. (qsort'ing the base
+ * arrays themselves would be simpler, but it doesn't work because pg_dump.c
+ * may have already established pointers between items.)
+ */
+static TableInfo *tblinfo;
+static TypeInfo *typinfo;
+static FuncInfo *funinfo;
+static OprInfo *oprinfo;
+static NamespaceInfo *nspinfo;
+static int numTables;
+static int numTypes;
+static int numFuncs;
+static int numOperators;
+static int numCollations;
+static int numNamespaces;
+static DumpableObject **tblinfoindex;
+static DumpableObject **typinfoindex;
+static DumpableObject **funinfoindex;
+static DumpableObject **oprinfoindex;
+static DumpableObject **collinfoindex;
+static DumpableObject **nspinfoindex;
+
+
+static void flagInhTables(TableInfo *tbinfo, int numTables,
+ InhInfo *inhinfo, int numInherits);
+static void flagInhAttrs(TableInfo *tblinfo, int numTables);
+static DumpableObject **buildIndexArray(void *objArray, int numObjs,
+ Size objSize);
+static int DOCatalogIdCompare(const void *p1, const void *p2);
+static void findParentsByOid(TableInfo *self,
+ InhInfo *inhinfo, int numInherits);
+static int strInArray(const char *pattern, char **arr, int arr_size);
+
+
+/*
+ * getSchemaData
+ * Collect information about all potentially dumpable objects
+ */
+TableInfo *
+getSchemaData(Archive *fout, int *numTablesPtr)
+{
+ ExtensionInfo *extinfo;
+ InhInfo *inhinfo;
+ CollInfo *collinfo;
+ int numExtensions;
+ int numAggregates;
+ int numInherits;
+ int numRules;
+ int numProcLangs;
+ int numCasts;
+ int numOpclasses;
+ int numOpfamilies;
+ int numConversions;
+ int numTSParsers;
+ int numTSTemplates;
+ int numTSDicts;
+ int numTSConfigs;
+ int numForeignDataWrappers;
+ int numForeignServers;
+ int numDefaultACLs;
+ int numEventTriggers;
+
+ if (g_verbose)
+ write_msg(NULL, "reading schemas\n");
+ nspinfo = getNamespaces(fout, &numNamespaces);
+ nspinfoindex = buildIndexArray(nspinfo, numNamespaces, sizeof(NamespaceInfo));
+
+ /*
+ * getTables should be done as soon as possible, so as to minimize the
+ * window between starting our transaction and acquiring per-table locks.
+ * However, we have to do getNamespaces first because the tables get
+ * linked to their containing namespaces during getTables.
+ */
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined tables\n");
+ tblinfo = getTables(fout, &numTables);
+ tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo));
+
+ /* Do this after we've built tblinfoindex */
+ getOwnedSeqs(fout, tblinfo, numTables);
+
+ if (g_verbose)
+ write_msg(NULL, "reading extensions\n");
+ extinfo = getExtensions(fout, &numExtensions);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined functions\n");
+ funinfo = getFuncs(fout, &numFuncs);
+ funinfoindex = buildIndexArray(funinfo, numFuncs, sizeof(FuncInfo));
+
+ /* this must be after getTables and getFuncs */
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined types\n");
+ typinfo = getTypes(fout, &numTypes);
+ typinfoindex = buildIndexArray(typinfo, numTypes, sizeof(TypeInfo));
+
+ /* this must be after getFuncs, too */
+ if (g_verbose)
+ write_msg(NULL, "reading procedural languages\n");
+ getProcLangs(fout, &numProcLangs);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined aggregate functions\n");
+ getAggregates(fout, &numAggregates);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined operators\n");
+ oprinfo = getOperators(fout, &numOperators);
+ oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo));
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined operator classes\n");
+ getOpclasses(fout, &numOpclasses);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined operator families\n");
+ getOpfamilies(fout, &numOpfamilies);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined text search parsers\n");
+ getTSParsers(fout, &numTSParsers);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined text search templates\n");
+ getTSTemplates(fout, &numTSTemplates);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined text search dictionaries\n");
+ getTSDictionaries(fout, &numTSDicts);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined text search configurations\n");
+ getTSConfigurations(fout, &numTSConfigs);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined foreign-data wrappers\n");
+ getForeignDataWrappers(fout, &numForeignDataWrappers);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined foreign servers\n");
+ getForeignServers(fout, &numForeignServers);
+
+ if (g_verbose)
+ write_msg(NULL, "reading default privileges\n");
+ getDefaultACLs(fout, &numDefaultACLs);
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined collations\n");
+ collinfo = getCollations(fout, &numCollations);
+ collinfoindex = buildIndexArray(collinfo, numCollations, sizeof(CollInfo));
+
+ if (g_verbose)
+ write_msg(NULL, "reading user-defined conversions\n");
+ getConversions(fout, &numConversions);
+
+ if (g_verbose)
+ write_msg(NULL, "reading type casts\n");
+ getCasts(fout, &numCasts);
+
+ if (g_verbose)
+ write_msg(NULL, "reading table inheritance information\n");
+ inhinfo = getInherits(fout, &numInherits);
+
+ if (g_verbose)
+ write_msg(NULL, "reading event triggers\n");
+ getEventTriggers(fout, &numEventTriggers);
+
+ /*
+ * Identify extension member objects and mark them as not to be dumped.
+ * This must happen after reading all objects that can be direct members
+ * of extensions, but before we begin to process table subsidiary objects.
+ */
+ if (g_verbose)
+ write_msg(NULL, "finding extension members\n");
+ getExtensionMembership(fout, extinfo, numExtensions);
+
+ /* Link tables to parents, mark parents of target tables interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding inheritance relationships\n");
+ flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+
+ if (g_verbose)
+ write_msg(NULL, "reading column info for interesting tables\n");
+ getTableAttrs(fout, tblinfo, numTables);
+
+ if (g_verbose)
+ write_msg(NULL, "flagging inherited columns in subtables\n");
+ flagInhAttrs(tblinfo, numTables);
+
+ if (g_verbose)
+ write_msg(NULL, "reading indexes\n");
+ getIndexes(fout, tblinfo, numTables);
+
+ if (g_verbose)
+ write_msg(NULL, "reading constraints\n");
+ getConstraints(fout, tblinfo, numTables);
+
+ if (g_verbose)
+ write_msg(NULL, "reading triggers\n");
+ getTriggers(fout, tblinfo, numTables);
+
+ if (g_verbose)
+ write_msg(NULL, "reading rewrite rules\n");
+ getRules(fout, &numRules);
+
+ *numTablesPtr = numTables;
+ return tblinfo;
+}
+
+/* flagInhTables -
+ * Fill in parent link fields of every target table, and mark
+ * parents of target tables as interesting
+ *
+ * Note that only direct ancestors of targets are marked interesting.
+ * This is sufficient; we don't much care whether they inherited their
+ * attributes or not.
+ *
+ * modifies tblinfo
+ */
+static void
+flagInhTables(TableInfo *tblinfo, int numTables,
+ InhInfo *inhinfo, int numInherits)
+{
+ int i,
+ j;
+ int numParents;
+ TableInfo **parents;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds never have parents */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find all the immediate parent tables */
+ findParentsByOid(&tblinfo[i], inhinfo, numInherits);
+
+ /* Mark the parents as interesting for getTableAttrs */
+ numParents = tblinfo[i].numParents;
+ parents = tblinfo[i].parents;
+ for (j = 0; j < numParents; j++)
+ parents[j]->interesting = true;
+ }
+}
+
+/* flagInhAttrs -
+ * for each dumpable table in tblinfo, flag its inherited attributes
+ *
+ * What we need to do here is detect child columns that inherit NOT NULL
+ * bits from their parents (so that we needn't specify that again for the
+ * child) and child columns that have DEFAULT NULL when their parents had
+ * some non-null default. In the latter case, we make up a dummy AttrDefInfo
+ * object so that we'll correctly emit the necessary DEFAULT NULL clause;
+ * otherwise the backend will apply an inherited default to the column.
+ *
+ * modifies tblinfo
+ */
+static void
+flagInhAttrs(TableInfo *tblinfo, int numTables)
+{
+ int i,
+ j,
+ k;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &(tblinfo[i]);
+ int numParents;
+ TableInfo **parents;
+
+ /* Some kinds never have parents */
+ if (tbinfo->relkind == RELKIND_SEQUENCE ||
+ tbinfo->relkind == RELKIND_VIEW ||
+ tbinfo->relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ numParents = tbinfo->numParents;
+ parents = tbinfo->parents;
+
+ if (numParents == 0)
+ continue; /* nothing to see here, move along */
+
+ /* For each column, search for matching column names in parent(s) */
+ for (j = 0; j < tbinfo->numatts; j++)
+ {
+ bool foundNotNull; /* Attr was NOT NULL in a parent */
+ bool foundDefault; /* Found a default in a parent */
+
+ /* no point in examining dropped columns */
+ if (tbinfo->attisdropped[j])
+ continue;
+
+ foundNotNull = false;
+ foundDefault = false;
+ for (k = 0; k < numParents; k++)
+ {
+ TableInfo *parent = parents[k];
+ int inhAttrInd;
+
+ inhAttrInd = strInArray(tbinfo->attnames[j],
+ parent->attnames,
+ parent->numatts);
+ if (inhAttrInd >= 0)
+ {
+ foundNotNull |= parent->notnull[inhAttrInd];
+ foundDefault |= (parent->attrdefs[inhAttrInd] != NULL);
+ }
+ }
+
+ /* Remember if we found inherited NOT NULL */
+ tbinfo->inhNotNull[j] = foundNotNull;
+
+ /* Manufacture a DEFAULT NULL clause if necessary */
+ if (foundDefault && tbinfo->attrdefs[j] == NULL)
+ {
+ AttrDefInfo *attrDef;
+
+ attrDef = (AttrDefInfo *) pg_malloc(sizeof(AttrDefInfo));
+ attrDef->dobj.objType = DO_ATTRDEF;
+ attrDef->dobj.catId.tableoid = 0;
+ attrDef->dobj.catId.oid = 0;
+ AssignDumpId(&attrDef->dobj);
+ attrDef->dobj.name = pg_strdup(tbinfo->dobj.name);
+ attrDef->dobj.namespace = tbinfo->dobj.namespace;
+ attrDef->dobj.dump = tbinfo->dobj.dump;
+
+ attrDef->adtable = tbinfo;
+ attrDef->adnum = j + 1;
+ attrDef->adef_expr = pg_strdup("NULL");
+
+ /* Will column be dumped explicitly? */
+ if (shouldPrintColumn(tbinfo, j))
+ {
+ attrDef->separate = false;
+ /* No dependency needed: NULL cannot have dependencies */
+ }
+ else
+ {
+ /* column will be suppressed, print default separately */
+ attrDef->separate = true;
+ /* ensure it comes out after the table */
+ addObjectDependency(&attrDef->dobj,
+ tbinfo->dobj.dumpId);
+ }
+
+ tbinfo->attrdefs[j] = attrDef;
+ }
+ }
+ }
+}
+
+/*
+ * AssignDumpId
+ * Given a newly-created dumpable object, assign a dump ID,
+ * and enter the object into the lookup table.
+ *
+ * The caller is expected to have filled in objType and catId,
+ * but not any of the other standard fields of a DumpableObject.
+ */
+void
+AssignDumpId(DumpableObject *dobj)
+{
+ dobj->dumpId = ++lastDumpId;
+ dobj->name = NULL; /* must be set later */
+ dobj->namespace = NULL; /* may be set later */
+ dobj->dump = true; /* default assumption */
+ dobj->ext_member = false; /* default assumption */
+ dobj->dependencies = NULL;
+ dobj->nDeps = 0;
+ dobj->allocDeps = 0;
+
+ while (dobj->dumpId >= allocedDumpIds)
+ {
+ int newAlloc;
+
+ if (allocedDumpIds <= 0)
+ {
+ newAlloc = 256;
+ dumpIdMap = (DumpableObject **)
+ pg_malloc(newAlloc * sizeof(DumpableObject *));
+ }
+ else
+ {
+ newAlloc = allocedDumpIds * 2;
+ dumpIdMap = (DumpableObject **)
+ pg_realloc(dumpIdMap, newAlloc * sizeof(DumpableObject *));
+ }
+ memset(dumpIdMap + allocedDumpIds, 0,
+ (newAlloc - allocedDumpIds) * sizeof(DumpableObject *));
+ allocedDumpIds = newAlloc;
+ }
+ dumpIdMap[dobj->dumpId] = dobj;
+
+ /* mark catalogIdMap invalid, but don't rebuild it yet */
+ catalogIdMapValid = false;
+}
+
+/*
+ * Assign a DumpId that's not tied to a DumpableObject.
+ *
+ * This is used when creating a "fixed" ArchiveEntry that doesn't need to
+ * participate in the sorting logic.
+ */
+DumpId
+createDumpId(void)
+{
+ return ++lastDumpId;
+}
+
+/*
+ * Return the largest DumpId so far assigned
+ */
+DumpId
+getMaxDumpId(void)
+{
+ return lastDumpId;
+}
+
+/*
+ * Find a DumpableObject by dump ID
+ *
+ * Returns NULL for invalid ID
+ */
+DumpableObject *
+findObjectByDumpId(DumpId dumpId)
+{
+ if (dumpId <= 0 || dumpId >= allocedDumpIds)
+ return NULL; /* out of range? */
+ return dumpIdMap[dumpId];
+}
+
+/*
+ * Find a DumpableObject by catalog ID
+ *
+ * Returns NULL for unknown ID
+ *
+ * We use binary search in a sorted list that is built on first call.
+ * If AssignDumpId() and findObjectByCatalogId() calls were freely intermixed,
+ * the code would work, but possibly be very slow. In the current usage
+ * pattern that does not happen, indeed we build the list at most twice.
+ */
+DumpableObject *
+findObjectByCatalogId(CatalogId catalogId)
+{
+ DumpableObject **low;
+ DumpableObject **high;
+
+ if (!catalogIdMapValid)
+ {
+ if (catalogIdMap)
+ free(catalogIdMap);
+ getDumpableObjects(&catalogIdMap, &numCatalogIds);
+ if (numCatalogIds > 1)
+ qsort((void *) catalogIdMap, numCatalogIds,
+ sizeof(DumpableObject *), DOCatalogIdCompare);
+ catalogIdMapValid = true;
+ }
+
+ /*
+ * We could use bsearch() here, but the notational cruft of calling
+ * bsearch is nearly as bad as doing it ourselves; and the generalized
+ * bsearch function is noticeably slower as well.
+ */
+ if (numCatalogIds <= 0)
+ return NULL;
+ low = catalogIdMap;
+ high = catalogIdMap + (numCatalogIds - 1);
+ while (low <= high)
+ {
+ DumpableObject **middle;
+ int difference;
+
+ middle = low + (high - low) / 2;
+ /* comparison must match DOCatalogIdCompare, below */
+ difference = oidcmp((*middle)->catId.oid, catalogId.oid);
+ if (difference == 0)
+ difference = oidcmp((*middle)->catId.tableoid, catalogId.tableoid);
+ if (difference == 0)
+ return *middle;
+ else if (difference < 0)
+ low = middle + 1;
+ else
+ high = middle - 1;
+ }
+ return NULL;
+}
+
+/*
+ * Find a DumpableObject by OID, in a pre-sorted array of one type of object
+ *
+ * Returns NULL for unknown OID
+ */
+static DumpableObject *
+findObjectByOid(Oid oid, DumpableObject **indexArray, int numObjs)
+{
+ DumpableObject **low;
+ DumpableObject **high;
+
+ /*
+ * This is the same as findObjectByCatalogId except we assume we need not
+ * look at table OID because the objects are all the same type.
+ *
+ * We could use bsearch() here, but the notational cruft of calling
+ * bsearch is nearly as bad as doing it ourselves; and the generalized
+ * bsearch function is noticeably slower as well.
+ */
+ if (numObjs <= 0)
+ return NULL;
+ low = indexArray;
+ high = indexArray + (numObjs - 1);
+ while (low <= high)
+ {
+ DumpableObject **middle;
+ int difference;
+
+ middle = low + (high - low) / 2;
+ difference = oidcmp((*middle)->catId.oid, oid);
+ if (difference == 0)
+ return *middle;
+ else if (difference < 0)
+ low = middle + 1;
+ else
+ high = middle - 1;
+ }
+ return NULL;
+}
+
+/*
+ * Build an index array of DumpableObject pointers, sorted by OID
+ */
+static DumpableObject **
+buildIndexArray(void *objArray, int numObjs, Size objSize)
+{
+ DumpableObject **ptrs;
+ int i;
+
+ ptrs = (DumpableObject **) pg_malloc(numObjs * sizeof(DumpableObject *));
+ for (i = 0; i < numObjs; i++)
+ ptrs[i] = (DumpableObject *) ((char *) objArray + i * objSize);
+
+ /* We can use DOCatalogIdCompare to sort since its first key is OID */
+ if (numObjs > 1)
+ qsort((void *) ptrs, numObjs, sizeof(DumpableObject *),
+ DOCatalogIdCompare);
+
+ return ptrs;
+}
+
+/*
+ * qsort comparator for pointers to DumpableObjects
+ */
+static int
+DOCatalogIdCompare(const void *p1, const void *p2)
+{
+ const DumpableObject *obj1 = *(DumpableObject *const *) p1;
+ const DumpableObject *obj2 = *(DumpableObject *const *) p2;
+ int cmpval;
+
+ /*
+ * Compare OID first since it's usually unique, whereas there will only be
+ * a few distinct values of tableoid.
+ */
+ cmpval = oidcmp(obj1->catId.oid, obj2->catId.oid);
+ if (cmpval == 0)
+ cmpval = oidcmp(obj1->catId.tableoid, obj2->catId.tableoid);
+ return cmpval;
+}
+
+/*
+ * Build an array of pointers to all known dumpable objects
+ *
+ * This simply creates a modifiable copy of the internal map.
+ */
+void
+getDumpableObjects(DumpableObject ***objs, int *numObjs)
+{
+ int i,
+ j;
+
+ *objs = (DumpableObject **)
+ pg_malloc(allocedDumpIds * sizeof(DumpableObject *));
+ j = 0;
+ for (i = 1; i < allocedDumpIds; i++)
+ {
+ if (dumpIdMap[i])
+ (*objs)[j++] = dumpIdMap[i];
+ }
+ *numObjs = j;
+}
+
+/*
+ * Add a dependency link to a DumpableObject
+ *
+ * Note: duplicate dependencies are currently not eliminated
+ */
+void
+addObjectDependency(DumpableObject *dobj, DumpId refId)
+{
+ if (dobj->nDeps >= dobj->allocDeps)
+ {
+ if (dobj->allocDeps <= 0)
+ {
+ dobj->allocDeps = 16;
+ dobj->dependencies = (DumpId *)
+ pg_malloc(dobj->allocDeps * sizeof(DumpId));
+ }
+ else
+ {
+ dobj->allocDeps *= 2;
+ dobj->dependencies = (DumpId *)
+ pg_realloc(dobj->dependencies,
+ dobj->allocDeps * sizeof(DumpId));
+ }
+ }
+ dobj->dependencies[dobj->nDeps++] = refId;
+}
+
+/*
+ * Remove a dependency link from a DumpableObject
+ *
+ * If there are multiple links, all are removed
+ */
+void
+removeObjectDependency(DumpableObject *dobj, DumpId refId)
+{
+ int i;
+ int j = 0;
+
+ for (i = 0; i < dobj->nDeps; i++)
+ {
+ if (dobj->dependencies[i] != refId)
+ dobj->dependencies[j++] = dobj->dependencies[i];
+ }
+ dobj->nDeps = j;
+}
+
+
+/*
+ * findTableByOid
+ * finds the entry (in tblinfo) of the table with the given oid
+ * returns NULL if not found
+ */
+TableInfo *
+findTableByOid(Oid oid)
+{
+ return (TableInfo *) findObjectByOid(oid, tblinfoindex, numTables);
+}
+
+/*
+ * findTypeByOid
+ * finds the entry (in typinfo) of the type with the given oid
+ * returns NULL if not found
+ */
+TypeInfo *
+findTypeByOid(Oid oid)
+{
+ return (TypeInfo *) findObjectByOid(oid, typinfoindex, numTypes);
+}
+
+/*
+ * findFuncByOid
+ * finds the entry (in funinfo) of the function with the given oid
+ * returns NULL if not found
+ */
+FuncInfo *
+findFuncByOid(Oid oid)
+{
+ return (FuncInfo *) findObjectByOid(oid, funinfoindex, numFuncs);
+}
+
+/*
+ * findOprByOid
+ * finds the entry (in oprinfo) of the operator with the given oid
+ * returns NULL if not found
+ */
+OprInfo *
+findOprByOid(Oid oid)
+{
+ return (OprInfo *) findObjectByOid(oid, oprinfoindex, numOperators);
+}
+
+/*
+ * findCollationByOid
+ * finds the entry (in collinfo) of the collation with the given oid
+ * returns NULL if not found
+ */
+CollInfo *
+findCollationByOid(Oid oid)
+{
+ return (CollInfo *) findObjectByOid(oid, collinfoindex, numCollations);
+}
+
+/*
+ * findNamespaceByOid
+ * finds the entry (in nspinfo) of the namespace with the given oid
+ * returns NULL if not found
+ */
+NamespaceInfo *
+findNamespaceByOid(Oid oid)
+{
+ return (NamespaceInfo *) findObjectByOid(oid, nspinfoindex, numNamespaces);
+}
+
+
+/*
+ * findParentsByOid
+ * find a table's parents in tblinfo[]
+ */
+static void
+findParentsByOid(TableInfo *self,
+ InhInfo *inhinfo, int numInherits)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i,
+ j;
+ int numParents;
+
+ numParents = 0;
+ for (i = 0; i < numInherits; i++)
+ {
+ if (inhinfo[i].inhrelid == oid)
+ numParents++;
+ }
+
+ self->numParents = numParents;
+
+ if (numParents > 0)
+ {
+ self->parents = (TableInfo **)
+ pg_malloc(sizeof(TableInfo *) * numParents);
+ j = 0;
+ for (i = 0; i < numInherits; i++)
+ {
+ if (inhinfo[i].inhrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(inhinfo[i].inhparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ inhinfo[i].inhparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->parents[j++] = parent;
+ }
+ }
+ }
+ else
+ self->parents = NULL;
+}
+
+/*
+ * parseOidArray
+ * parse a string of numbers delimited by spaces into a character array
+ *
+ * Note: actually this is used for both Oids and potentially-signed
+ * attribute numbers. This should cause no trouble, but we could split
+ * the function into two functions with different argument types if it does.
+ */
+
+void
+parseOidArray(const char *str, Oid *array, int arraysize)
+{
+ int j,
+ argNum;
+ char temp[100];
+ char s;
+
+ argNum = 0;
+ j = 0;
+ for (;;)
+ {
+ s = *str++;
+ if (s == ' ' || s == '\0')
+ {
+ if (j > 0)
+ {
+ if (argNum >= arraysize)
+ {
+ write_msg(NULL, "could not parse numeric array \"%s\": too many numbers\n", str);
+ exit_nicely(1);
+ }
+ temp[j] = '\0';
+ array[argNum++] = atooid(temp);
+ j = 0;
+ }
+ if (s == '\0')
+ break;
+ }
+ else
+ {
+ if (!(isdigit((unsigned char) s) || s == '-') ||
+ j >= sizeof(temp) - 1)
+ {
+ write_msg(NULL, "could not parse numeric array \"%s\": invalid character in number\n", str);
+ exit_nicely(1);
+ }
+ temp[j++] = s;
+ }
+ }
+
+ while (argNum < arraysize)
+ array[argNum++] = InvalidOid;
+}
+
+
+/*
+ * strInArray:
+ * takes in a string and a string array and the number of elements in the
+ * string array.
+ * returns the index if the string is somewhere in the array, -1 otherwise
+ */
+
+static int
+strInArray(const char *pattern, char **arr, int arr_size)
+{
+ int i;
+
+ for (i = 0; i < arr_size; i++)
+ {
+ if (strcmp(pattern, arr[i]) == 0)
+ return i;
+ }
+ return -1;
+}
+
+
+/*
+ * Support for simple list operations
+ */
+
+void
+simple_oid_list_append(SimpleOidList *list, Oid val)
+{
+ SimpleOidListCell *cell;
+
+ cell = (SimpleOidListCell *) pg_malloc(sizeof(SimpleOidListCell));
+ cell->next = NULL;
+ cell->val = val;
+
+ if (list->tail)
+ list->tail->next = cell;
+ else
+ list->head = cell;
+ list->tail = cell;
+}
+
+bool
+simple_oid_list_member(SimpleOidList *list, Oid val)
+{
+ SimpleOidListCell *cell;
+
+ for (cell = list->head; cell; cell = cell->next)
+ {
+ if (cell->val == val)
+ return true;
+ }
+ return false;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/compat.h
@@ -0,0 +1,17 @@
+#ifndef COMPAT_H
+#define COMPAT_H
+
+#if !defined(pg_attribute_printf)
+
+/* GCC and XLC support format attributes */
+#if defined(__GNUC__) || defined(__IBMC__)
+#define pg_attribute_format_arg(a) __attribute__((format_arg(a)))
+#define pg_attribute_printf(f,a) __attribute__((format(PG_PRINTF_ATTRIBUTE, f, a)))
+#else
+#define pg_attribute_format_arg(a)
+#define pg_attribute_printf(f,a)
+#endif
+
+#endif
+
+#endif
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/compress_io.c
@@ -0,0 +1,722 @@
+/*-------------------------------------------------------------------------
+ *
+ * compress_io.c
+ * Routines for archivers to write an uncompressed or compressed data
+ * stream.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * This file includes two APIs for dealing with compressed data. The first
+ * provides more flexibility, using callbacks to read/write data from the
+ * underlying stream. The second API is a wrapper around fopen/gzopen and
+ * friends, providing an interface similar to those, but abstracts away
+ * the possible compression. Both APIs use libz for the compression, but
+ * the second API uses gzip headers, so the resulting files can be easily
+ * manipulated with the gzip utility.
+ *
+ * Compressor API
+ * --------------
+ *
+ * The interface for writing to an archive consists of three functions:
+ * AllocateCompressor, WriteDataToArchive and EndCompressor. First you call
+ * AllocateCompressor, then write all the data by calling WriteDataToArchive
+ * as many times as needed, and finally EndCompressor. WriteDataToArchive
+ * and EndCompressor will call the WriteFunc that was provided to
+ * AllocateCompressor for each chunk of compressed data.
+ *
+ * The interface for reading an archive consists of just one function:
+ * ReadDataFromArchive. ReadDataFromArchive reads the whole compressed input
+ * stream, by repeatedly calling the given ReadFunc. ReadFunc returns the
+ * compressed data chunk at a time, and ReadDataFromArchive decompresses it
+ * and passes the decompressed data to ahwrite(), until ReadFunc returns 0
+ * to signal EOF.
+ *
+ * The interface is the same for compressed and uncompressed streams.
+ *
+ * Compressed stream API
+ * ----------------------
+ *
+ * The compressed stream API is a wrapper around the C standard fopen() and
+ * libz's gzopen() APIs. It allows you to use the same functions for
+ * compressed and uncompressed streams. cfopen_read() first tries to open
+ * the file with given name, and if it fails, it tries to open the same
+ * file with the .gz suffix. cfopen_write() opens a file for writing, an
+ * extra argument specifies if the file should be compressed, and adds the
+ * .gz suffix to the filename if so. This allows you to easily handle both
+ * compressed and uncompressed files.
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/compress_io.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "compress_io.h"
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+/*----------------------
+ * Compressor API
+ *----------------------
+ */
+
+/* typedef appears in compress_io.h */
+struct CompressorState
+{
+ CompressionAlgorithm comprAlg;
+ WriteFunc writeF;
+
+#ifdef HAVE_LIBZ
+ z_streamp zp;
+ char *zlibOut;
+ size_t zlibOutSize;
+#endif
+};
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("compress_io");
+
+static void ParseCompressionOption(int compression, CompressionAlgorithm *alg,
+ int *level);
+
+/* Routines that support zlib compressed data I/O */
+#ifdef HAVE_LIBZ
+static void InitCompressorZlib(CompressorState *cs, int level);
+static void DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs,
+ bool flush);
+static void ReadDataFromArchiveZlib(ArchiveHandle *AH, ReadFunc readF);
+static void WriteDataToArchiveZlib(ArchiveHandle *AH, CompressorState *cs,
+ const char *data, size_t dLen);
+static void EndCompressorZlib(ArchiveHandle *AH, CompressorState *cs);
+#endif
+
+/* Routines that support uncompressed data I/O */
+static void ReadDataFromArchiveNone(ArchiveHandle *AH, ReadFunc readF);
+static void WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
+ const char *data, size_t dLen);
+
+/*
+ * Interprets a numeric 'compression' value. The algorithm implied by the
+ * value (zlib or none at the moment), is returned in *alg, and the
+ * zlib compression level in *level.
+ */
+static void
+ParseCompressionOption(int compression, CompressionAlgorithm *alg, int *level)
+{
+ if (compression == Z_DEFAULT_COMPRESSION ||
+ (compression > 0 && compression <= 9))
+ *alg = COMPR_ALG_LIBZ;
+ else if (compression == 0)
+ *alg = COMPR_ALG_NONE;
+ else
+ {
+ exit_horribly(modulename, "invalid compression code: %d\n",
+ compression);
+ *alg = COMPR_ALG_NONE; /* keep compiler quiet */
+ }
+
+ /* The level is just the passed-in value. */
+ if (level)
+ *level = compression;
+}
+
+/* Public interface routines */
+
+/* Allocate a new compressor */
+CompressorState *
+AllocateCompressor(int compression, WriteFunc writeF)
+{
+ CompressorState *cs;
+ CompressionAlgorithm alg;
+ int level;
+
+ ParseCompressionOption(compression, &alg, &level);
+
+#ifndef HAVE_LIBZ
+ if (alg == COMPR_ALG_LIBZ)
+ exit_horribly(modulename, "not built with zlib support\n");
+#endif
+
+ cs = (CompressorState *) pg_malloc0(sizeof(CompressorState));
+ cs->writeF = writeF;
+ cs->comprAlg = alg;
+
+ /*
+ * Perform compression algorithm specific initialization.
+ */
+#ifdef HAVE_LIBZ
+ if (alg == COMPR_ALG_LIBZ)
+ InitCompressorZlib(cs, level);
+#endif
+
+ return cs;
+}
+
+/*
+ * Read all compressed data from the input stream (via readF) and print it
+ * out with ahwrite().
+ */
+void
+ReadDataFromArchive(ArchiveHandle *AH, int compression, ReadFunc readF)
+{
+ CompressionAlgorithm alg;
+
+ ParseCompressionOption(compression, &alg, NULL);
+
+ if (alg == COMPR_ALG_NONE)
+ ReadDataFromArchiveNone(AH, readF);
+ if (alg == COMPR_ALG_LIBZ)
+ {
+#ifdef HAVE_LIBZ
+ ReadDataFromArchiveZlib(AH, readF);
+#else
+ exit_horribly(modulename, "not built with zlib support\n");
+#endif
+ }
+}
+
+/*
+ * Compress and write data to the output stream (via writeF).
+ */
+void
+WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
+ const void *data, size_t dLen)
+{
+ /* Are we aborting? */
+ checkAborting(AH);
+
+ switch (cs->comprAlg)
+ {
+ case COMPR_ALG_LIBZ:
+#ifdef HAVE_LIBZ
+ WriteDataToArchiveZlib(AH, cs, data, dLen);
+#else
+ exit_horribly(modulename, "not built with zlib support\n");
+#endif
+ break;
+ case COMPR_ALG_NONE:
+ WriteDataToArchiveNone(AH, cs, data, dLen);
+ break;
+ }
+ return;
+}
+
+/*
+ * Terminate compression library context and flush its buffers.
+ */
+void
+EndCompressor(ArchiveHandle *AH, CompressorState *cs)
+{
+#ifdef HAVE_LIBZ
+ if (cs->comprAlg == COMPR_ALG_LIBZ)
+ EndCompressorZlib(AH, cs);
+#endif
+ free(cs);
+}
+
+/* Private routines, specific to each compression method. */
+
+#ifdef HAVE_LIBZ
+/*
+ * Functions for zlib compressed output.
+ */
+
+static void
+InitCompressorZlib(CompressorState *cs, int level)
+{
+ z_streamp zp;
+
+ zp = cs->zp = (z_streamp) pg_malloc(sizeof(z_stream));
+ zp->zalloc = Z_NULL;
+ zp->zfree = Z_NULL;
+ zp->opaque = Z_NULL;
+
+ /*
+ * zlibOutSize is the buffer size we tell zlib it can output to. We
+ * actually allocate one extra byte because some routines want to append a
+ * trailing zero byte to the zlib output.
+ */
+ cs->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
+ cs->zlibOutSize = ZLIB_OUT_SIZE;
+
+ if (deflateInit(zp, level) != Z_OK)
+ exit_horribly(modulename,
+ "could not initialize compression library: %s\n",
+ zp->msg);
+
+ /* Just be paranoid - maybe End is called after Start, with no Write */
+ zp->next_out = (void *) cs->zlibOut;
+ zp->avail_out = cs->zlibOutSize;
+}
+
+static void
+EndCompressorZlib(ArchiveHandle *AH, CompressorState *cs)
+{
+ z_streamp zp = cs->zp;
+
+ zp->next_in = NULL;
+ zp->avail_in = 0;
+
+ /* Flush any remaining data from zlib buffer */
+ DeflateCompressorZlib(AH, cs, true);
+
+ if (deflateEnd(zp) != Z_OK)
+ exit_horribly(modulename,
+ "could not close compression stream: %s\n", zp->msg);
+
+ free(cs->zlibOut);
+ free(cs->zp);
+}
+
+static void
+DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs, bool flush)
+{
+ z_streamp zp = cs->zp;
+ char *out = cs->zlibOut;
+ int res = Z_OK;
+
+ while (cs->zp->avail_in != 0 || flush)
+ {
+ res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH);
+ if (res == Z_STREAM_ERROR)
+ exit_horribly(modulename,
+ "could not compress data: %s\n", zp->msg);
+ if ((flush && (zp->avail_out < cs->zlibOutSize))
+ || (zp->avail_out == 0)
+ || (zp->avail_in != 0)
+ )
+ {
+ /*
+ * Extra paranoia: avoid zero-length chunks, since a zero length
+ * chunk is the EOF marker in the custom format. This should never
+ * happen but...
+ */
+ if (zp->avail_out < cs->zlibOutSize)
+ {
+ /*
+ * Any write function shoud do its own error checking but to
+ * make sure we do a check here as well...
+ */
+ size_t len = cs->zlibOutSize - zp->avail_out;
+
+ cs->writeF(AH, out, len);
+ }
+ zp->next_out = (void *) out;
+ zp->avail_out = cs->zlibOutSize;
+ }
+
+ if (res == Z_STREAM_END)
+ break;
+ }
+}
+
+static void
+WriteDataToArchiveZlib(ArchiveHandle *AH, CompressorState *cs,
+ const char *data, size_t dLen)
+{
+ cs->zp->next_in = (void *) data;
+ cs->zp->avail_in = dLen;
+ DeflateCompressorZlib(AH, cs, false);
+
+ return;
+}
+
+static void
+ReadDataFromArchiveZlib(ArchiveHandle *AH, ReadFunc readF)
+{
+ z_streamp zp;
+ char *out;
+ int res = Z_OK;
+ size_t cnt;
+ char *buf;
+ size_t buflen;
+
+ zp = (z_streamp) pg_malloc(sizeof(z_stream));
+ zp->zalloc = Z_NULL;
+ zp->zfree = Z_NULL;
+ zp->opaque = Z_NULL;
+
+ buf = pg_malloc(ZLIB_IN_SIZE);
+ buflen = ZLIB_IN_SIZE;
+
+ out = pg_malloc(ZLIB_OUT_SIZE + 1);
+
+ if (inflateInit(zp) != Z_OK)
+ exit_horribly(modulename,
+ "could not initialize compression library: %s\n",
+ zp->msg);
+
+ /* no minimal chunk size for zlib */
+ while ((cnt = readF(AH, &buf, &buflen)))
+ {
+ /* Are we aborting? */
+ checkAborting(AH);
+
+ zp->next_in = (void *) buf;
+ zp->avail_in = cnt;
+
+ while (zp->avail_in > 0)
+ {
+ zp->next_out = (void *) out;
+ zp->avail_out = ZLIB_OUT_SIZE;
+
+ res = inflate(zp, 0);
+ if (res != Z_OK && res != Z_STREAM_END)
+ exit_horribly(modulename,
+ "could not uncompress data: %s\n", zp->msg);
+
+ out[ZLIB_OUT_SIZE - zp->avail_out] = '\0';
+ ahwrite(out, 1, ZLIB_OUT_SIZE - zp->avail_out, AH);
+ }
+ }
+
+ zp->next_in = NULL;
+ zp->avail_in = 0;
+ while (res != Z_STREAM_END)
+ {
+ zp->next_out = (void *) out;
+ zp->avail_out = ZLIB_OUT_SIZE;
+ res = inflate(zp, 0);
+ if (res != Z_OK && res != Z_STREAM_END)
+ exit_horribly(modulename,
+ "could not uncompress data: %s\n", zp->msg);
+
+ out[ZLIB_OUT_SIZE - zp->avail_out] = '\0';
+ ahwrite(out, 1, ZLIB_OUT_SIZE - zp->avail_out, AH);
+ }
+
+ if (inflateEnd(zp) != Z_OK)
+ exit_horribly(modulename,
+ "could not close compression library: %s\n", zp->msg);
+
+ free(buf);
+ free(out);
+ free(zp);
+}
+#endif /* HAVE_LIBZ */
+
+
+/*
+ * Functions for uncompressed output.
+ */
+
+static void
+ReadDataFromArchiveNone(ArchiveHandle *AH, ReadFunc readF)
+{
+ size_t cnt;
+ char *buf;
+ size_t buflen;
+
+ buf = pg_malloc(ZLIB_OUT_SIZE);
+ buflen = ZLIB_OUT_SIZE;
+
+ while ((cnt = readF(AH, &buf, &buflen)))
+ {
+ /* Are we aborting? */
+ checkAborting(AH);
+
+ ahwrite(buf, 1, cnt, AH);
+ }
+
+ free(buf);
+}
+
+static void
+WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
+ const char *data, size_t dLen)
+{
+ cs->writeF(AH, data, dLen);
+ return;
+}
+
+
+/*----------------------
+ * Compressed stream API
+ *----------------------
+ */
+
+/*
+ * cfp represents an open stream, wrapping the underlying FILE or gzFile
+ * pointer. This is opaque to the callers.
+ */
+struct cfp
+{
+ FILE *uncompressedfp;
+#ifdef HAVE_LIBZ
+ gzFile compressedfp;
+#endif
+};
+
+#ifdef HAVE_LIBZ
+static int hasSuffix(const char *filename, const char *suffix);
+#endif
+
+/* free() without changing errno; useful in several places below */
+static void
+free_keep_errno(void *p)
+{
+ int save_errno = errno;
+
+ free(p);
+ errno = save_errno;
+}
+
+/*
+ * Open a file for reading. 'path' is the file to open, and 'mode' should
+ * be either "r" or "rb".
+ *
+ * If the file at 'path' does not exist, we append the ".gz" suffix (if 'path'
+ * doesn't already have it) and try again. So if you pass "foo" as 'path',
+ * this will open either "foo" or "foo.gz".
+ *
+ * On failure, return NULL with an error code in errno.
+ */
+cfp *
+cfopen_read(const char *path, const char *mode)
+{
+ cfp *fp;
+
+#ifdef HAVE_LIBZ
+ if (hasSuffix(path, ".gz"))
+ fp = cfopen(path, mode, 1);
+ else
+#endif
+ {
+ fp = cfopen(path, mode, 0);
+#ifdef HAVE_LIBZ
+ if (fp == NULL)
+ {
+ char *fname;
+
+ fname = psprintf("%s.gz", path);
+ fp = cfopen(fname, mode, 1);
+ free_keep_errno(fname);
+ }
+#endif
+ }
+ return fp;
+}
+
+/*
+ * Open a file for writing. 'path' indicates the path name, and 'mode' must
+ * be a filemode as accepted by fopen() and gzopen() that indicates writing
+ * ("w", "wb", "a", or "ab").
+ *
+ * If 'compression' is non-zero, a gzip compressed stream is opened, and
+ * 'compression' indicates the compression level used. The ".gz" suffix
+ * is automatically added to 'path' in that case.
+ *
+ * On failure, return NULL with an error code in errno.
+ */
+cfp *
+cfopen_write(const char *path, const char *mode, int compression)
+{
+ cfp *fp;
+
+ if (compression == 0)
+ fp = cfopen(path, mode, 0);
+ else
+ {
+#ifdef HAVE_LIBZ
+ char *fname;
+
+ fname = psprintf("%s.gz", path);
+ fp = cfopen(fname, mode, compression);
+ free_keep_errno(fname);
+#else
+ exit_horribly(modulename, "not built with zlib support\n");
+ fp = NULL; /* keep compiler quiet */
+#endif
+ }
+ return fp;
+}
+
+/*
+ * Opens file 'path' in 'mode'. If 'compression' is non-zero, the file
+ * is opened with libz gzopen(), otherwise with plain fopen().
+ *
+ * On failure, return NULL with an error code in errno.
+ */
+cfp *
+cfopen(const char *path, const char *mode, int compression)
+{
+ cfp *fp = pg_malloc(sizeof(cfp));
+
+ if (compression != 0)
+ {
+#ifdef HAVE_LIBZ
+ if (compression != Z_DEFAULT_COMPRESSION)
+ {
+ /* user has specified a compression level, so tell zlib to use it */
+ char mode_compression[32];
+
+ snprintf(mode_compression, sizeof(mode_compression), "%s%d",
+ mode, compression);
+ fp->compressedfp = gzopen(path, mode_compression);
+ }
+ else
+ {
+ /* don't specify a level, just use the zlib default */
+ fp->compressedfp = gzopen(path, mode);
+ }
+
+ fp->uncompressedfp = NULL;
+ if (fp->compressedfp == NULL)
+ {
+ free_keep_errno(fp);
+ fp = NULL;
+ }
+#else
+ exit_horribly(modulename, "not built with zlib support\n");
+#endif
+ }
+ else
+ {
+#ifdef HAVE_LIBZ
+ fp->compressedfp = NULL;
+#endif
+ fp->uncompressedfp = fopen(path, mode);
+ if (fp->uncompressedfp == NULL)
+ {
+ free_keep_errno(fp);
+ fp = NULL;
+ }
+ }
+
+ return fp;
+}
+
+
+int
+cfread(void *ptr, int size, cfp *fp)
+{
+ int ret;
+
+ if (size == 0)
+ return 0;
+
+#ifdef HAVE_LIBZ
+ if (fp->compressedfp)
+ {
+ ret = gzread(fp->compressedfp, ptr, size);
+ if (ret != size && !gzeof(fp->compressedfp))
+ exit_horribly(modulename,
+ "could not read from input file: %s\n", strerror(errno));
+ }
+ else
+#endif
+ {
+ ret = fread(ptr, 1, size, fp->uncompressedfp);
+ if (ret != size && !feof(fp->uncompressedfp))
+ READ_ERROR_EXIT(fp->uncompressedfp);
+ }
+ return ret;
+}
+
+int
+cfwrite(const void *ptr, int size, cfp *fp)
+{
+#ifdef HAVE_LIBZ
+ if (fp->compressedfp)
+ return gzwrite(fp->compressedfp, ptr, size);
+ else
+#endif
+ return fwrite(ptr, 1, size, fp->uncompressedfp);
+}
+
+int
+cfgetc(cfp *fp)
+{
+ int ret;
+
+#ifdef HAVE_LIBZ
+ if (fp->compressedfp)
+ {
+ ret = gzgetc(fp->compressedfp);
+ if (ret == EOF)
+ {
+ if (!gzeof(fp->compressedfp))
+ exit_horribly(modulename,
+ "could not read from input file: %s\n", strerror(errno));
+ else
+ exit_horribly(modulename,
+ "could not read from input file: end of file\n");
+ }
+ }
+ else
+#endif
+ {
+ ret = fgetc(fp->uncompressedfp);
+ if (ret == EOF)
+ READ_ERROR_EXIT(fp->uncompressedfp);
+ }
+
+ return ret;
+}
+
+char *
+cfgets(cfp *fp, char *buf, int len)
+{
+#ifdef HAVE_LIBZ
+ if (fp->compressedfp)
+ return gzgets(fp->compressedfp, buf, len);
+ else
+#endif
+ return fgets(buf, len, fp->uncompressedfp);
+}
+
+int
+cfclose(cfp *fp)
+{
+ int result;
+
+ if (fp == NULL)
+ {
+ errno = EBADF;
+ return EOF;
+ }
+#ifdef HAVE_LIBZ
+ if (fp->compressedfp)
+ {
+ result = gzclose(fp->compressedfp);
+ fp->compressedfp = NULL;
+ }
+ else
+#endif
+ {
+ result = fclose(fp->uncompressedfp);
+ fp->uncompressedfp = NULL;
+ }
+ free_keep_errno(fp);
+
+ return result;
+}
+
+int
+cfeof(cfp *fp)
+{
+#ifdef HAVE_LIBZ
+ if (fp->compressedfp)
+ return gzeof(fp->compressedfp);
+ else
+#endif
+ return feof(fp->uncompressedfp);
+}
+
+#ifdef HAVE_LIBZ
+static int
+hasSuffix(const char *filename, const char *suffix)
+{
+ int filenamelen = strlen(filename);
+ int suffixlen = strlen(suffix);
+
+ if (filenamelen < suffixlen)
+ return 0;
+
+ return memcmp(&filename[filenamelen - suffixlen],
+ suffix,
+ suffixlen) == 0;
+}
+
+#endif
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/compress_io.h
@@ -0,0 +1,70 @@
+/*-------------------------------------------------------------------------
+ *
+ * compress_io.h
+ * Interface to compress_io.c routines
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/compress_io.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef __COMPRESS_IO__
+#define __COMPRESS_IO__
+
+#include "postgres_fe.h"
+#include "pg_backup_archiver.h"
+
+/* Initial buffer sizes used in zlib compression. */
+#define ZLIB_OUT_SIZE 4096
+#define ZLIB_IN_SIZE 4096
+
+typedef enum
+{
+ COMPR_ALG_NONE,
+ COMPR_ALG_LIBZ
+} CompressionAlgorithm;
+
+/* Prototype for callback function to WriteDataToArchive() */
+typedef void (*WriteFunc) (ArchiveHandle *AH, const char *buf, size_t len);
+
+/*
+ * Prototype for callback function to ReadDataFromArchive()
+ *
+ * ReadDataFromArchive will call the read function repeatedly, until it
+ * returns 0 to signal EOF. ReadDataFromArchive passes a buffer to read the
+ * data into in *buf, of length *buflen. If that's not big enough for the
+ * callback function, it can free() it and malloc() a new one, returning the
+ * new buffer and its size in *buf and *buflen.
+ *
+ * Returns the number of bytes read into *buf, or 0 on EOF.
+ */
+typedef size_t (*ReadFunc) (ArchiveHandle *AH, char **buf, size_t *buflen);
+
+/* struct definition appears in compress_io.c */
+typedef struct CompressorState CompressorState;
+
+extern CompressorState *AllocateCompressor(int compression, WriteFunc writeF);
+extern void ReadDataFromArchive(ArchiveHandle *AH, int compression,
+ ReadFunc readF);
+extern void WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
+ const void *data, size_t dLen);
+extern void EndCompressor(ArchiveHandle *AH, CompressorState *cs);
+
+
+typedef struct cfp cfp;
+
+extern cfp *cfopen(const char *path, const char *mode, int compression);
+extern cfp *cfopen_read(const char *path, const char *mode);
+extern cfp *cfopen_write(const char *path, const char *mode, int compression);
+extern int cfread(void *ptr, int size, cfp *fp);
+extern int cfwrite(const void *ptr, int size, cfp *fp);
+extern int cfgetc(cfp *fp);
+extern char *cfgets(cfp *fp, char *buf, int len);
+extern int cfclose(cfp *fp);
+extern int cfeof(cfp *fp);
+
+#endif
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/dumputils.c
@@ -0,0 +1,1244 @@
+/*-------------------------------------------------------------------------
+ *
+ * Utility routines for SQL dumping
+ * Basically this is stuff that is useful in both pg_dump and pg_dumpall.
+ * Lately it's also being used by psql and bin/scripts/ ...
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_dump/dumputils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <ctype.h>
+
+#include "dumputils.h"
+
+#include "parser/keywords.h"
+
+
+/* Globals from keywords.c */
+extern const ScanKeyword FEScanKeywords[];
+extern const int NumFEScanKeywords;
+
+#define supports_grant_options(version) ((version) >= 70400)
+
+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 void AddAcl(PQExpBuffer aclbuf, const char *keyword,
+ const char *subname);
+static PQExpBuffer defaultGetLocalPQExpBuffer(void);
+
+/* Globals exported by this file */
+int quote_all_identifiers = 0;
+PQExpBuffer (*getLocalPQExpBuffer) (void) = defaultGetLocalPQExpBuffer;
+
+/*
+ * Returns a temporary PQExpBuffer, valid until the next call to the function.
+ * This is used by fmtId and fmtQualifiedId.
+ *
+ * Non-reentrant and non-thread-safe but reduces memory leakage. You can
+ * replace this with a custom version by setting the getLocalPQExpBuffer
+ * function pointer.
+ */
+static PQExpBuffer
+defaultGetLocalPQExpBuffer(void)
+{
+ static PQExpBuffer id_return = NULL;
+
+ if (id_return) /* first time through? */
+ {
+ /* same buffer, just wipe contents */
+ resetPQExpBuffer(id_return);
+ }
+ else
+ {
+ /* new buffer */
+ id_return = createPQExpBuffer();
+ }
+
+ return id_return;
+}
+
+/*
+ * Quotes input string if it's not a legitimate SQL identifier as-is.
+ *
+ * Note that the returned string must be used before calling fmtId again,
+ * since we re-use the same return buffer each time.
+ */
+const char *
+fmtId(const char *rawid)
+{
+ PQExpBuffer id_return = getLocalPQExpBuffer();
+
+ const char *cp;
+ bool need_quotes = false;
+
+ /*
+ * These checks need to match the identifier production in scan.l. Don't
+ * use islower() etc.
+ */
+ if (quote_all_identifiers)
+ need_quotes = true;
+ /* slightly different rules for first character */
+ else if (!((rawid[0] >= 'a' && rawid[0] <= 'z') || rawid[0] == '_'))
+ need_quotes = true;
+ else
+ {
+ /* otherwise check the entire string */
+ for (cp = rawid; *cp; cp++)
+ {
+ if (!((*cp >= 'a' && *cp <= 'z')
+ || (*cp >= '0' && *cp <= '9')
+ || (*cp == '_')))
+ {
+ need_quotes = true;
+ break;
+ }
+ }
+ }
+
+ if (!need_quotes)
+ {
+ /*
+ * Check for keyword. We quote keywords except for unreserved ones.
+ * (In some cases we could avoid quoting a col_name or type_func_name
+ * keyword, but it seems much harder than it's worth to tell that.)
+ *
+ * Note: ScanKeywordLookup() does case-insensitive comparison, but
+ * that's fine, since we already know we have all-lower-case.
+ */
+ const ScanKeyword *keyword = ScanKeywordLookup(rawid,
+ FEScanKeywords,
+ NumFEScanKeywords);
+
+ if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
+ need_quotes = true;
+ }
+
+ if (!need_quotes)
+ {
+ /* no quoting needed */
+ appendPQExpBufferStr(id_return, rawid);
+ }
+ else
+ {
+ appendPQExpBufferChar(id_return, '\"');
+ for (cp = rawid; *cp; cp++)
+ {
+ /*
+ * Did we find a double-quote in the string? Then make this a
+ * double double-quote per SQL99. Before, we put in a
+ * backslash/double-quote pair. - thomas 2000-08-05
+ */
+ if (*cp == '\"')
+ appendPQExpBufferChar(id_return, '\"');
+ appendPQExpBufferChar(id_return, *cp);
+ }
+ appendPQExpBufferChar(id_return, '\"');
+ }
+
+ return id_return->data;
+}
+
+/*
+ * fmtQualifiedId - convert a qualified name to the proper format for
+ * the source database.
+ *
+ * Like fmtId, use the result before calling again.
+ *
+ * Since we call fmtId and it also uses getThreadLocalPQExpBuffer() we cannot
+ * use it until we're finished with calling fmtId().
+ */
+const char *
+fmtQualifiedId(int remoteVersion, const char *schema, const char *id)
+{
+ PQExpBuffer id_return;
+ PQExpBuffer lcl_pqexp = createPQExpBuffer();
+
+ /* Suppress schema name if fetching from pre-7.3 DB */
+ if (remoteVersion >= 70300 && schema && *schema)
+ {
+ appendPQExpBuffer(lcl_pqexp, "%s.", fmtId(schema));
+ }
+ appendPQExpBufferStr(lcl_pqexp, fmtId(id));
+
+ id_return = getLocalPQExpBuffer();
+
+ appendPQExpBufferStr(id_return, lcl_pqexp->data);
+ destroyPQExpBuffer(lcl_pqexp);
+
+ return id_return->data;
+}
+
+/*
+ * Convert a string value to an SQL string literal and append it to
+ * the given buffer. We assume the specified client_encoding and
+ * standard_conforming_strings settings.
+ *
+ * This is essentially equivalent to libpq's PQescapeStringInternal,
+ * except for the output buffer structure. We need it in situations
+ * where we do not have a PGconn available. Where we do,
+ * appendStringLiteralConn is a better choice.
+ */
+void
+appendStringLiteral(PQExpBuffer buf, const char *str,
+ int encoding, bool std_strings)
+{
+ size_t length = strlen(str);
+ const char *source = str;
+ char *target;
+
+ if (!enlargePQExpBuffer(buf, 2 * length + 2))
+ return;
+
+ target = buf->data + buf->len;
+ *target++ = '\'';
+
+ while (*source != '\0')
+ {
+ char c = *source;
+ int len;
+ int i;
+
+ /* Fast path for plain ASCII */
+ if (!IS_HIGHBIT_SET(c))
+ {
+ /* Apply quoting if needed */
+ if (SQL_STR_DOUBLE(c, !std_strings))
+ *target++ = c;
+ /* Copy the character */
+ *target++ = c;
+ source++;
+ continue;
+ }
+
+ /* Slow path for possible multibyte characters */
+ len = PQmblen(source, encoding);
+
+ /* Copy the character */
+ for (i = 0; i < len; i++)
+ {
+ if (*source == '\0')
+ break;
+ *target++ = *source++;
+ }
+
+ /*
+ * If we hit premature end of string (ie, incomplete multibyte
+ * character), try to pad out to the correct length with spaces. We
+ * may not be able to pad completely, but we will always be able to
+ * insert at least one pad space (since we'd not have quoted a
+ * multibyte character). This should be enough to make a string that
+ * the server will error out on.
+ */
+ if (i < len)
+ {
+ char *stop = buf->data + buf->maxlen - 2;
+
+ for (; i < len; i++)
+ {
+ if (target >= stop)
+ break;
+ *target++ = ' ';
+ }
+ break;
+ }
+ }
+
+ /* Write the terminating quote and NUL character. */
+ *target++ = '\'';
+ *target = '\0';
+
+ buf->len = target - buf->data;
+}
+
+
+/*
+ * Convert a string value to an SQL string literal and append it to
+ * the given buffer. Encoding and string syntax rules are as indicated
+ * by current settings of the PGconn.
+ */
+void
+appendStringLiteralConn(PQExpBuffer buf, const char *str, PGconn *conn)
+{
+ size_t length = strlen(str);
+
+ /*
+ * XXX This is a kluge to silence escape_string_warning in our utility
+ * programs. It should go away someday.
+ */
+ if (strchr(str, '\\') != NULL && PQserverVersion(conn) >= 80100)
+ {
+ /* ensure we are not adjacent to an identifier */
+ if (buf->len > 0 && buf->data[buf->len - 1] != ' ')
+ appendPQExpBufferChar(buf, ' ');
+ appendPQExpBufferChar(buf, ESCAPE_STRING_SYNTAX);
+ appendStringLiteral(buf, str, PQclientEncoding(conn), false);
+ return;
+ }
+ /* XXX end kluge */
+
+ if (!enlargePQExpBuffer(buf, 2 * length + 2))
+ return;
+ appendPQExpBufferChar(buf, '\'');
+ buf->len += PQescapeStringConn(conn, buf->data + buf->len,
+ str, length, NULL);
+ appendPQExpBufferChar(buf, '\'');
+}
+
+
+/*
+ * Convert a string value to a dollar quoted literal and append it to
+ * the given buffer. If the dqprefix parameter is not NULL then the
+ * dollar quote delimiter will begin with that (after the opening $).
+ *
+ * No escaping is done at all on str, in compliance with the rules
+ * for parsing dollar quoted strings. Also, we need not worry about
+ * encoding issues.
+ */
+void
+appendStringLiteralDQ(PQExpBuffer buf, const char *str, const char *dqprefix)
+{
+ static const char suffixes[] = "_XXXXXXX";
+ int nextchar = 0;
+ PQExpBuffer delimBuf = createPQExpBuffer();
+
+ /* start with $ + dqprefix if not NULL */
+ appendPQExpBufferChar(delimBuf, '$');
+ if (dqprefix)
+ appendPQExpBufferStr(delimBuf, dqprefix);
+
+ /*
+ * Make sure we choose a delimiter which (without the trailing $) is not
+ * present in the string being quoted. We don't check with the trailing $
+ * because a string ending in $foo must not be quoted with $foo$.
+ */
+ while (strstr(str, delimBuf->data) != NULL)
+ {
+ appendPQExpBufferChar(delimBuf, suffixes[nextchar++]);
+ nextchar %= sizeof(suffixes) - 1;
+ }
+
+ /* add trailing $ */
+ appendPQExpBufferChar(delimBuf, '$');
+
+ /* quote it and we are all done */
+ appendPQExpBufferStr(buf, delimBuf->data);
+ appendPQExpBufferStr(buf, str);
+ appendPQExpBufferStr(buf, delimBuf->data);
+
+ destroyPQExpBuffer(delimBuf);
+}
+
+
+/*
+ * Convert a bytea value (presented as raw bytes) to an SQL string literal
+ * and append it to the given buffer. We assume the specified
+ * standard_conforming_strings setting.
+ *
+ * This is needed in situations where we do not have a PGconn available.
+ * Where we do, PQescapeByteaConn is a better choice.
+ */
+void
+appendByteaLiteral(PQExpBuffer buf, const unsigned char *str, size_t length,
+ bool std_strings)
+{
+ const unsigned char *source = str;
+ char *target;
+
+ static const char hextbl[] = "0123456789abcdef";
+
+ /*
+ * This implementation is hard-wired to produce hex-format output. We do
+ * not know the server version the output will be loaded into, so making
+ * an intelligent format choice is impossible. It might be better to
+ * always use the old escaped format.
+ */
+ if (!enlargePQExpBuffer(buf, 2 * length + 5))
+ return;
+
+ target = buf->data + buf->len;
+ *target++ = '\'';
+ if (!std_strings)
+ *target++ = '\\';
+ *target++ = '\\';
+ *target++ = 'x';
+
+ while (length-- > 0)
+ {
+ unsigned char c = *source++;
+
+ *target++ = hextbl[(c >> 4) & 0xF];
+ *target++ = hextbl[c & 0xF];
+ }
+
+ /* Write the terminating quote and NUL character. */
+ *target++ = '\'';
+ *target = '\0';
+
+ buf->len = target - buf->data;
+}
+
+
+/*
+ * Deconstruct the text representation of a 1-dimensional Postgres array
+ * into individual items.
+ *
+ * On success, returns true and sets *itemarray and *nitems to describe
+ * an array of individual strings. On parse failure, returns false;
+ * *itemarray may exist or be NULL.
+ *
+ * NOTE: free'ing itemarray is sufficient to deallocate the working storage.
+ */
+bool
+parsePGArray(const char *atext, char ***itemarray, int *nitems)
+{
+ int inputlen;
+ char **items;
+ char *strings;
+ int curitem;
+
+ /*
+ * We expect input in the form of "{item,item,item}" where any item is
+ * either raw data, or surrounded by double quotes (in which case embedded
+ * characters including backslashes and quotes are backslashed).
+ *
+ * We build the result as an array of pointers followed by the actual
+ * string data, all in one malloc block for convenience of deallocation.
+ * The worst-case storage need is not more than one pointer and one
+ * character for each input character (consider "{,,,,,,,,,,}").
+ */
+ *itemarray = NULL;
+ *nitems = 0;
+ inputlen = strlen(atext);
+ if (inputlen < 2 || atext[0] != '{' || atext[inputlen - 1] != '}')
+ return false; /* bad input */
+ items = (char **) malloc(inputlen * (sizeof(char *) + sizeof(char)));
+ if (items == NULL)
+ return false; /* out of memory */
+ *itemarray = items;
+ strings = (char *) (items + inputlen);
+
+ atext++; /* advance over initial '{' */
+ curitem = 0;
+ while (*atext != '}')
+ {
+ if (*atext == '\0')
+ return false; /* premature end of string */
+ items[curitem] = strings;
+ while (*atext != '}' && *atext != ',')
+ {
+ if (*atext == '\0')
+ return false; /* premature end of string */
+ if (*atext != '"')
+ *strings++ = *atext++; /* copy unquoted data */
+ else
+ {
+ /* process quoted substring */
+ atext++;
+ while (*atext != '"')
+ {
+ if (*atext == '\0')
+ return false; /* premature end of string */
+ if (*atext == '\\')
+ {
+ atext++;
+ if (*atext == '\0')
+ return false; /* premature end of string */
+ }
+ *strings++ = *atext++; /* copy quoted data */
+ }
+ atext++;
+ }
+ }
+ *strings++ = '\0';
+ if (*atext == ',')
+ atext++;
+ curitem++;
+ }
+ if (atext[1] != '\0')
+ return false; /* bogus syntax (embedded '}') */
+ *nitems = curitem;
+ return true;
+}
+
+
+/*
+ * Build GRANT/REVOKE command(s) for an object.
+ *
+ * name: the object name, in the form to use in the commands (already quoted)
+ * subname: the sub-object name, if any (already quoted); NULL if none
+ * type: the object type (as seen in GRANT command: must be one of
+ * TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
+ * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
+ * acls: the ACL string fetched from the database
+ * 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
+ * remoteVersion: version of database
+ *
+ * Returns TRUE if okay, FALSE if could not parse the acl string.
+ * The resulting commands (if any) are appended to the contents of 'sql'.
+ *
+ * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES "
+ * or something similar, and name is an empty string.
+ *
+ * Note: beware of passing a fmtId() result directly as 'name' or 'subname',
+ * since this routine uses fmtId() internally.
+ */
+bool
+buildACLCommands(const char *name, const char *subname,
+ const char *type, const char *acls, const char *owner,
+ const char *prefix, int remoteVersion,
+ PQExpBuffer sql)
+{
+ bool ok = true;
+ char **aclitems;
+ int naclitems;
+ int i;
+ PQExpBuffer grantee,
+ grantor,
+ privs,
+ privswgo;
+ PQExpBuffer firstsql,
+ secondsql;
+ bool found_owner_privs = false;
+
+ if (strlen(acls) == 0)
+ return true; /* object has default permissions */
+
+ /* treat empty-string owner same as NULL */
+ if (owner && *owner == '\0')
+ owner = NULL;
+
+ if (!parsePGArray(acls, &aclitems, &naclitems))
+ {
+ if (aclitems)
+ free(aclitems);
+ return false;
+ }
+
+ grantee = createPQExpBuffer();
+ grantor = createPQExpBuffer();
+ privs = createPQExpBuffer();
+ privswgo = createPQExpBuffer();
+
+ /*
+ * At the end, these two will be pasted together to form the result. But
+ * the owner privileges need to go before the other ones to keep the
+ * dependencies valid. In recent versions this is normally the case, but
+ * in old versions they come after the PUBLIC privileges and that results
+ * in problems if we need to run REVOKE on the owner privileges.
+ */
+ firstsql = createPQExpBuffer();
+ secondsql = createPQExpBuffer();
+
+ /*
+ * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to
+ * wire-in knowledge about the default public privileges for different
+ * kinds of objects.
+ */
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+
+ /*
+ * 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,
+ * which is generally not what's wanted. So add back default privs if the
+ * source database is too old to have had that particular priv.
+ */
+ if (remoteVersion < 80200 && strcmp(type, "DATABASE") == 0)
+ {
+ /* database CONNECT priv didn't exist before 8.2 */
+ appendPQExpBuffer(firstsql, "%sGRANT CONNECT ON %s %s TO PUBLIC;\n",
+ prefix, type, name);
+ }
+
+ /* Scan individual ACL items */
+ for (i = 0; i < naclitems; 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 (owner
+ && strcmp(grantee->data, owner) == 0
+ && strcmp(grantor->data, owner) == 0)
+ {
+ found_owner_privs = true;
+
+ /*
+ * For the owner, the default privilege level is ALL WITH
+ * GRANT OPTION (only ALL prior to 7.4).
+ */
+ if (supports_grant_options(remoteVersion)
+ ? strcmp(privswgo->data, "ALL") != 0
+ : strcmp(privs->data, "ALL") != 0)
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+ type, name, fmtId(grantee->data));
+ if (privs->len > 0)
+ appendPQExpBuffer(firstsql,
+ "%sGRANT %s ON %s %s TO %s;\n",
+ prefix, privs->data, type, name,
+ fmtId(grantee->data));
+ if (privswgo->len > 0)
+ appendPQExpBuffer(firstsql,
+ "%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
+ prefix, privswgo->data, type, name,
+ fmtId(grantee->data));
+ }
+ }
+ else
+ {
+ /*
+ * Otherwise can assume we are starting from no privs.
+ */
+ if (grantor->len > 0
+ && (!owner || strcmp(owner, grantor->data) != 0))
+ appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n",
+ fmtId(grantor->data));
+
+ if (privs->len > 0)
+ {
+ appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
+ prefix, privs->data, type, name);
+ if (grantee->len == 0)
+ appendPQExpBufferStr(secondsql, "PUBLIC;\n");
+ else if (strncmp(grantee->data, "group ",
+ strlen("group ")) == 0)
+ appendPQExpBuffer(secondsql, "GROUP %s;\n",
+ fmtId(grantee->data + strlen("group ")));
+ else
+ appendPQExpBuffer(secondsql, "%s;\n", fmtId(grantee->data));
+ }
+ if (privswgo->len > 0)
+ {
+ appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
+ prefix, privswgo->data, type, name);
+ if (grantee->len == 0)
+ appendPQExpBufferStr(secondsql, "PUBLIC");
+ else if (strncmp(grantee->data, "group ",
+ strlen("group ")) == 0)
+ appendPQExpBuffer(secondsql, "GROUP %s",
+ fmtId(grantee->data + strlen("group ")));
+ else
+ appendPQExpBufferStr(secondsql, fmtId(grantee->data));
+ appendPQExpBufferStr(secondsql, " WITH GRANT OPTION;\n");
+ }
+
+ if (grantor->len > 0
+ && (!owner || strcmp(owner, grantor->data) != 0))
+ appendPQExpBufferStr(secondsql, "RESET SESSION AUTHORIZATION;\n");
+ }
+ }
+ }
+
+ /*
+ * If we didn't find any owner privs, the owner must have revoked 'em all
+ */
+ if (!found_owner_privs && owner)
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+ type, name, fmtId(owner));
+ }
+
+ destroyPQExpBuffer(grantee);
+ destroyPQExpBuffer(grantor);
+ destroyPQExpBuffer(privs);
+ destroyPQExpBuffer(privswgo);
+
+ appendPQExpBuffer(sql, "%s%s", firstsql->data, secondsql->data);
+ destroyPQExpBuffer(firstsql);
+ destroyPQExpBuffer(secondsql);
+
+ free(aclitems);
+
+ return ok;
+}
+
+/*
+ * Build ALTER DEFAULT PRIVILEGES command(s) for 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
+ * owner: username of privileges owner (will be passed through fmtId)
+ * remoteVersion: version of database
+ *
+ * Returns TRUE if okay, FALSE if could not parse the acl string.
+ * The resulting commands (if any) are appended to the contents of 'sql'.
+ */
+bool
+buildDefaultACLCommands(const char *type, const char *nspname,
+ const char *acls, const char *owner,
+ int remoteVersion,
+ PQExpBuffer sql)
+{
+ bool result;
+ PQExpBuffer prefix;
+
+ prefix = createPQExpBuffer();
+
+ /*
+ * We incorporate the target role directly into the command, rather than
+ * playing around with SET ROLE or anything like that. This is so that a
+ * permissions error leads to nothing happening, rather than changing
+ * default privileges for the wrong user.
+ */
+ appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s ",
+ fmtId(owner));
+ if (nspname)
+ appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
+
+ result = buildACLCommands("", NULL,
+ type, acls, owner,
+ prefix->data, remoteVersion,
+ sql);
+
+ destroyPQExpBuffer(prefix);
+
+ return result;
+}
+
+/*
+ * This will parse an aclitem string, having the general form
+ * username=privilegecodes/grantor
+ * or
+ * group groupname=privilegecodes/grantor
+ * (the /grantor part will not be present if pre-7.4 database).
+ *
+ * The returned grantee string will be the dequoted username or groupname
+ * (preceded with "group " in the latter case). The returned grantor is
+ * the dequoted grantor name or empty. Privilege characters are decoded
+ * and split between privileges with grant option (privswgo) and without
+ * (privs).
+ *
+ * Note: for cross-version compatibility, it's important to use ALL when
+ * appropriate.
+ */
+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)
+{
+ char *buf;
+ bool all_with_go = true;
+ bool all_without_go = true;
+ char *eqpos;
+ char *slpos;
+ char *pos;
+
+ buf = strdup(item);
+ if (!buf)
+ return false;
+
+ /* user or group name is string up to = */
+ eqpos = copyAclUserName(grantee, buf);
+ if (*eqpos != '=')
+ {
+ free(buf);
+ return false;
+ }
+
+ /* grantor may be listed after / */
+ slpos = strchr(eqpos + 1, '/');
+ if (slpos)
+ {
+ *slpos++ = '\0';
+ slpos = copyAclUserName(grantor, slpos);
+ if (*slpos != '\0')
+ {
+ free(buf);
+ return false;
+ }
+ }
+ else
+ resetPQExpBuffer(grantor);
+
+ /* privilege codes */
+#define CONVERT_PRIV(code, keywd) \
+do { \
+ if ((pos = strchr(eqpos + 1, code))) \
+ { \
+ if (*(pos + 1) == '*') \
+ { \
+ AddAcl(privswgo, keywd, subname); \
+ all_without_go = false; \
+ } \
+ else \
+ { \
+ AddAcl(privs, keywd, subname); \
+ all_with_go = false; \
+ } \
+ } \
+ else \
+ all_with_go = all_without_go = false; \
+} while (0)
+
+ resetPQExpBuffer(privs);
+ resetPQExpBuffer(privswgo);
+
+ if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 ||
+ strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0)
+ {
+ CONVERT_PRIV('r', "SELECT");
+
+ if (strcmp(type, "SEQUENCE") == 0 ||
+ strcmp(type, "SEQUENCES") == 0)
+ /* sequence only */
+ CONVERT_PRIV('U', "USAGE");
+ else
+ {
+ /* table only */
+ CONVERT_PRIV('a', "INSERT");
+ if (remoteVersion >= 70200)
+ CONVERT_PRIV('x', "REFERENCES");
+ /* rest are not applicable to columns */
+ if (subname == NULL)
+ {
+ if (remoteVersion >= 70200)
+ {
+ CONVERT_PRIV('d', "DELETE");
+ CONVERT_PRIV('t', "TRIGGER");
+ }
+ if (remoteVersion >= 80400)
+ CONVERT_PRIV('D', "TRUNCATE");
+ }
+ }
+
+ /* UPDATE */
+ if (remoteVersion >= 70200 ||
+ strcmp(type, "SEQUENCE") == 0 ||
+ strcmp(type, "SEQUENCES") == 0)
+ CONVERT_PRIV('w', "UPDATE");
+ else
+ /* 7.0 and 7.1 have a simpler worldview */
+ CONVERT_PRIV('w', "UPDATE,DELETE");
+ }
+ else if (strcmp(type, "FUNCTION") == 0 ||
+ strcmp(type, "FUNCTIONS") == 0)
+ CONVERT_PRIV('X', "EXECUTE");
+ else if (strcmp(type, "LANGUAGE") == 0)
+ CONVERT_PRIV('U', "USAGE");
+ else if (strcmp(type, "SCHEMA") == 0)
+ {
+ CONVERT_PRIV('C', "CREATE");
+ CONVERT_PRIV('U', "USAGE");
+ }
+ else if (strcmp(type, "DATABASE") == 0)
+ {
+ CONVERT_PRIV('C', "CREATE");
+ CONVERT_PRIV('c', "CONNECT");
+ CONVERT_PRIV('T', "TEMPORARY");
+ }
+ else if (strcmp(type, "TABLESPACE") == 0)
+ CONVERT_PRIV('C', "CREATE");
+ else if (strcmp(type, "TYPE") == 0 ||
+ strcmp(type, "TYPES") == 0)
+ CONVERT_PRIV('U', "USAGE");
+ else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0)
+ CONVERT_PRIV('U', "USAGE");
+ else if (strcmp(type, "FOREIGN SERVER") == 0)
+ CONVERT_PRIV('U', "USAGE");
+ else if (strcmp(type, "FOREIGN TABLE") == 0)
+ CONVERT_PRIV('r', "SELECT");
+ else if (strcmp(type, "LARGE OBJECT") == 0)
+ {
+ CONVERT_PRIV('r', "SELECT");
+ CONVERT_PRIV('w', "UPDATE");
+ }
+ else
+ abort();
+
+#undef CONVERT_PRIV
+
+ if (all_with_go)
+ {
+ resetPQExpBuffer(privs);
+ printfPQExpBuffer(privswgo, "ALL");
+ if (subname)
+ appendPQExpBuffer(privswgo, "(%s)", subname);
+ }
+ else if (all_without_go)
+ {
+ resetPQExpBuffer(privswgo);
+ printfPQExpBuffer(privs, "ALL");
+ if (subname)
+ appendPQExpBuffer(privs, "(%s)", subname);
+ }
+
+ free(buf);
+
+ return true;
+}
+
+/*
+ * 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.
+ */
+static char *
+copyAclUserName(PQExpBuffer output, char *input)
+{
+ resetPQExpBuffer(output);
+
+ while (*input && *input != '=')
+ {
+ /*
+ * If user name isn't quoted, then just add it to the output buffer
+ */
+ if (*input != '"')
+ appendPQExpBufferChar(output, *input++);
+ else
+ {
+ /* Otherwise, it's a quoted username */
+ input++;
+ /* Loop until we come across an unescaped quote */
+ while (!(*input == '"' && *(input + 1) != '"'))
+ {
+ if (*input == '\0')
+ return input; /* really a syntax error... */
+
+ /*
+ * Quoting convention is to escape " as "". Keep this code in
+ * sync with putid() in backend's acl.c.
+ */
+ if (*input == '"' && *(input + 1) == '"')
+ input++;
+ appendPQExpBufferChar(output, *input++);
+ }
+ input++;
+ }
+ }
+ return input;
+}
+
+/*
+ * Append a privilege keyword to a keyword list, inserting comma if needed.
+ */
+static void
+AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname)
+{
+ if (aclbuf->len > 0)
+ appendPQExpBufferChar(aclbuf, ',');
+ appendPQExpBufferStr(aclbuf, keyword);
+ if (subname)
+ appendPQExpBuffer(aclbuf, "(%s)", subname);
+}
+
+
+/*
+ * processSQLNamePattern
+ *
+ * Scan a wildcard-pattern string and generate appropriate WHERE clauses
+ * to limit the set of objects returned. The WHERE clauses are appended
+ * to the already-partially-constructed query in buf. Returns whether
+ * any clause was added.
+ *
+ * conn: connection query will be sent to (consulted for escaping rules).
+ * buf: output parameter.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * have_where: true if caller already emitted "WHERE" (clauses will be ANDed
+ * onto the existing WHERE clause).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * schemavar: name of query variable to match against a schema-name pattern.
+ * Can be NULL if no schema.
+ * namevar: name of query variable to match against an object-name pattern.
+ * altnamevar: NULL, or name of an alternative variable to match against name.
+ * visibilityrule: clause to use if we want to restrict to visible objects
+ * (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ *
+ * Formatting note: the text already present in buf should end with a newline.
+ * The appended text, if any, will end with one too.
+ */
+bool
+processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar, const char *visibilityrule)
+{
+ PQExpBufferData schemabuf;
+ PQExpBufferData namebuf;
+ int encoding = PQclientEncoding(conn);
+ bool inquotes;
+ const char *cp;
+ int i;
+ bool added_clause = false;
+
+#define WHEREAND() \
+ (appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
+ have_where = true, added_clause = true)
+
+ if (pattern == NULL)
+ {
+ /* Default: select all visible objects */
+ if (visibilityrule)
+ {
+ WHEREAND();
+ appendPQExpBuffer(buf, "%s\n", visibilityrule);
+ }
+ return added_clause;
+ }
+
+ initPQExpBuffer(&schemabuf);
+ initPQExpBuffer(&namebuf);
+
+ /*
+ * Parse the pattern, converting quotes and lower-casing unquoted letters.
+ * Also, adjust shell-style wildcard characters into regexp notation.
+ *
+ * We surround the pattern with "^(...)$" to force it to match the whole
+ * string, as per SQL practice. We have to have parens in case the string
+ * contains "|", else the "^" and "$" will be bound into the first and
+ * last alternatives which is not what we want.
+ *
+ * Note: the result of this pass is the actual regexp pattern(s) we want
+ * to execute. Quoting/escaping into SQL literal format will be done
+ * below using appendStringLiteralConn().
+ */
+ appendPQExpBufferStr(&namebuf, "^(");
+
+ inquotes = false;
+ cp = pattern;
+
+ while (*cp)
+ {
+ char ch = *cp;
+
+ if (ch == '"')
+ {
+ if (inquotes && cp[1] == '"')
+ {
+ /* emit one quote, stay in inquotes mode */
+ appendPQExpBufferChar(&namebuf, '"');
+ cp++;
+ }
+ else
+ inquotes = !inquotes;
+ cp++;
+ }
+ else if (!inquotes && isupper((unsigned char) ch))
+ {
+ appendPQExpBufferChar(&namebuf,
+ pg_tolower((unsigned char) ch));
+ cp++;
+ }
+ else if (!inquotes && ch == '*')
+ {
+ appendPQExpBufferStr(&namebuf, ".*");
+ cp++;
+ }
+ else if (!inquotes && ch == '?')
+ {
+ appendPQExpBufferChar(&namebuf, '.');
+ cp++;
+ }
+ else if (!inquotes && ch == '.')
+ {
+ /* Found schema/name separator, move current pattern to schema */
+ resetPQExpBuffer(&schemabuf);
+ appendPQExpBufferStr(&schemabuf, namebuf.data);
+ resetPQExpBuffer(&namebuf);
+ appendPQExpBufferStr(&namebuf, "^(");
+ cp++;
+ }
+ else if (ch == '$')
+ {
+ /*
+ * Dollar is always quoted, whether inside quotes or not. The
+ * reason is that it's allowed in SQL identifiers, so there's a
+ * significant use-case for treating it literally, while because
+ * we anchor the pattern automatically there is no use-case for
+ * having it possess its regexp meaning.
+ */
+ appendPQExpBufferStr(&namebuf, "\\$");
+ cp++;
+ }
+ else
+ {
+ /*
+ * Ordinary data character, transfer to pattern
+ *
+ * Inside double quotes, or at all times if force_escape is true,
+ * quote regexp special characters with a backslash to avoid
+ * regexp errors. Outside quotes, however, let them pass through
+ * as-is; this lets knowledgeable users build regexp expressions
+ * that are more powerful than shell-style patterns.
+ */
+ if ((inquotes || force_escape) &&
+ strchr("|*+?()[]{}.^$\\", ch))
+ appendPQExpBufferChar(&namebuf, '\\');
+ i = PQmblen(cp, encoding);
+ while (i-- && *cp)
+ {
+ appendPQExpBufferChar(&namebuf, *cp);
+ cp++;
+ }
+ }
+ }
+
+ /*
+ * Now decide what we need to emit. Note there will be a leading "^(" in
+ * the patterns in any case.
+ */
+ if (namebuf.len > 2)
+ {
+ /* We have a name pattern, so constrain the namevar(s) */
+
+ appendPQExpBufferStr(&namebuf, ")$");
+ /* Optimize away a "*" pattern */
+ if (strcmp(namebuf.data, "^(.*)$") != 0)
+ {
+ WHEREAND();
+ if (altnamevar)
+ {
+ appendPQExpBuffer(buf, "(%s ~ ", namevar);
+ appendStringLiteralConn(buf, namebuf.data, conn);
+ appendPQExpBuffer(buf, "\n OR %s ~ ", altnamevar);
+ appendStringLiteralConn(buf, namebuf.data, conn);
+ appendPQExpBufferStr(buf, ")\n");
+ }
+ else
+ {
+ appendPQExpBuffer(buf, "%s ~ ", namevar);
+ appendStringLiteralConn(buf, namebuf.data, conn);
+ appendPQExpBufferChar(buf, '\n');
+ }
+ }
+ }
+
+ if (schemabuf.len > 2)
+ {
+ /* We have a schema pattern, so constrain the schemavar */
+
+ appendPQExpBufferStr(&schemabuf, ")$");
+ /* Optimize away a "*" pattern */
+ if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ {
+ WHEREAND();
+ appendPQExpBuffer(buf, "%s ~ ", schemavar);
+ appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendPQExpBufferChar(buf, '\n');
+ }
+ }
+ else
+ {
+ /* No schema pattern given, so select only visible objects */
+ if (visibilityrule)
+ {
+ WHEREAND();
+ appendPQExpBuffer(buf, "%s\n", visibilityrule);
+ }
+ }
+
+ termPQExpBuffer(&schemabuf);
+ termPQExpBuffer(&namebuf);
+
+ return added_clause;
+#undef WHEREAND
+}
+
+/*
+ * buildShSecLabelQuery
+ *
+ * Build a query to retrieve security labels for a shared object.
+ */
+void
+buildShSecLabelQuery(PGconn *conn, const char *catalog_name, uint32 objectId,
+ PQExpBuffer sql)
+{
+ appendPQExpBuffer(sql,
+ "SELECT provider, label FROM pg_catalog.pg_shseclabel "
+ "WHERE classoid = '%s'::pg_catalog.regclass AND "
+ "objoid = %u", catalog_name, objectId);
+}
+
+/*
+ * emitShSecLabels
+ *
+ * Format security label data retrieved by the query generated in
+ * buildShSecLabelQuery.
+ */
+void
+emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
+ const char *target, const char *objname)
+{
+ int i;
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ char *provider = PQgetvalue(res, i, 0);
+ char *label = PQgetvalue(res, i, 1);
+
+ /* must use fmtId result before calling it again */
+ appendPQExpBuffer(buffer,
+ "SECURITY LABEL FOR %s ON %s",
+ fmtId(provider), target);
+ appendPQExpBuffer(buffer,
+ " %s IS ",
+ fmtId(objname));
+ appendStringLiteralConn(buffer, label, conn);
+ appendPQExpBufferStr(buffer, ";\n");
+ }
+}
+
+
+void
+simple_string_list_append(SimpleStringList *list, const char *val)
+{
+ SimpleStringListCell *cell;
+
+ /* this calculation correctly accounts for the null trailing byte */
+ cell = (SimpleStringListCell *)
+ pg_malloc(sizeof(SimpleStringListCell) + strlen(val));
+
+ cell->next = NULL;
+ strcpy(cell->val, val);
+
+ if (list->tail)
+ list->tail->next = cell;
+ else
+ list->head = cell;
+ list->tail = cell;
+}
+
+bool
+simple_string_list_member(SimpleStringList *list, const char *val)
+{
+ SimpleStringListCell *cell;
+
+ for (cell = list->head; cell; cell = cell->next)
+ {
+ if (strcmp(cell->val, val) == 0)
+ return true;
+ }
+ return false;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/dumputils.h
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * Utility routines for SQL dumping
+ * Basically this is stuff that is useful in both pg_dump and pg_dumpall.
+ * Lately it's also being used by psql and bin/scripts/ ...
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_dump/dumputils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef DUMPUTILS_H
+#define DUMPUTILS_H
+
+#include "compat.h"
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+typedef struct SimpleStringListCell
+{
+ struct SimpleStringListCell *next;
+ char val[1]; /* VARIABLE LENGTH FIELD */
+} SimpleStringListCell;
+
+typedef struct SimpleStringList
+{
+ SimpleStringListCell *head;
+ SimpleStringListCell *tail;
+} SimpleStringList;
+
+
+extern int quote_all_identifiers;
+extern PQExpBuffer (*getLocalPQExpBuffer) (void);
+
+extern const char *fmtId(const char *identifier);
+extern const char *fmtQualifiedId(int remoteVersion,
+ const char *schema, const char *id);
+extern void appendStringLiteral(PQExpBuffer buf, const char *str,
+ int encoding, bool std_strings);
+extern void appendStringLiteralConn(PQExpBuffer buf, const char *str,
+ PGconn *conn);
+extern void appendStringLiteralDQ(PQExpBuffer buf, const char *str,
+ const char *dqprefix);
+extern void appendByteaLiteral(PQExpBuffer buf,
+ const unsigned char *str, size_t length,
+ bool std_strings);
+extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems);
+extern bool buildACLCommands(const char *name, const char *subname,
+ const char *type, const char *acls, const char *owner,
+ const char *prefix, int remoteVersion,
+ PQExpBuffer sql);
+extern bool buildDefaultACLCommands(const char *type, const char *nspname,
+ const char *acls, const char *owner,
+ int remoteVersion,
+ PQExpBuffer sql);
+extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
+ const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar, const char *visibilityrule);
+extern void buildShSecLabelQuery(PGconn *conn, const char *catalog_name,
+ uint32 objectId, PQExpBuffer sql);
+extern void emitShSecLabels(PGconn *conn, PGresult *res,
+ PQExpBuffer buffer, const char *target, const char *objname);
+extern void set_dump_section(const char *arg, int *dumpSections);
+
+extern void simple_string_list_append(SimpleStringList *list, const char *val);
+extern bool simple_string_list_member(SimpleStringList *list, const char *val);
+
+#endif /* DUMPUTILS_H */
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/keywords.c
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+ *
+ * keywords.c
+ * lexical token lookup for key words in PostgreSQL
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/keywords.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include "parser/keywords.h"
+
+/*
+ * We don't need the token number, so leave it out to avoid requiring other
+ * backend headers.
+ */
+#define PG_KEYWORD(a,b,c) {a,0,c},
+
+const ScanKeyword FEScanKeywords[] = {
+#include "parser/kwlist.h"
+};
+
+const int NumFEScanKeywords = lengthof(FEScanKeywords);
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/kwlookup.c
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * kwlookup.c
+ * lexical token lookup for key words in PostgreSQL
+ *
+ * NB - this file is also used by ECPG and several frontend programs in
+ * src/bin/ including pg_dump and psql
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/parser/kwlookup.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* use c.h so this can be built as either frontend or backend */
+#include "c.h"
+
+#include <ctype.h>
+
+#include "parser/keywords.h"
+
+/*
+ * ScanKeywordLookup - see if a given word is a keyword
+ *
+ * Returns a pointer to the ScanKeyword table entry, or NULL if no match.
+ *
+ * The match is done case-insensitively. Note that we deliberately use a
+ * dumbed-down case conversion that will only translate 'A'-'Z' into 'a'-'z',
+ * even if we are in a locale where tolower() would produce more or different
+ * translations. This is to conform to the SQL99 spec, which says that
+ * keywords are to be matched in this way even though non-keyword identifiers
+ * receive a different case-normalization mapping.
+ */
+const ScanKeyword *
+ScanKeywordLookup(const char *text,
+ const ScanKeyword *keywords,
+ int num_keywords)
+{
+ int len,
+ i;
+ char word[NAMEDATALEN];
+ const ScanKeyword *low;
+ const ScanKeyword *high;
+
+ len = strlen(text);
+ /* We assume all keywords are shorter than NAMEDATALEN. */
+ if (len >= NAMEDATALEN)
+ return NULL;
+
+ /*
+ * Apply an ASCII-only downcasing. We must not use tolower() since it may
+ * produce the wrong translation in some locales (eg, Turkish).
+ */
+ for (i = 0; i < len; i++)
+ {
+ char ch = text[i];
+
+ if (ch >= 'A' && ch <= 'Z')
+ ch += 'a' - 'A';
+ word[i] = ch;
+ }
+ word[len] = '\0';
+
+ /*
+ * Now do a binary search using plain strcmp() comparison.
+ */
+ low = keywords;
+ high = keywords + (num_keywords - 1);
+ while (low <= high)
+ {
+ const ScanKeyword *middle;
+ int difference;
+
+ middle = low + (high - low) / 2;
+ difference = strcmp(middle->name, word);
+ if (difference == 0)
+ return middle;
+ else if (difference < 0)
+ low = middle + 1;
+ else
+ high = middle - 1;
+ }
+
+ return NULL;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/parallel.c
@@ -0,0 +1,1417 @@
+/*-------------------------------------------------------------------------
+ *
+ * parallel.c
+ *
+ * Parallel support for the pg_dump archiver
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * The author is not responsible for loss or damages that may
+ * result from its use.
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/parallel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "signal.h"
+#include <unistd.h>
+#include <fcntl.h>
+#endif
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+/* file-scope variables */
+#ifdef WIN32
+static unsigned int tMasterThreadId = 0;
+static HANDLE termEvent = INVALID_HANDLE_VALUE;
+static int pgpipe(int handles[2]);
+static int piperead(int s, char *buf, int len);
+
+/*
+ * Structure to hold info passed by _beginthreadex() to the function it calls
+ * via its single allowed argument.
+ */
+typedef struct
+{
+ ArchiveHandle *AH;
+ RestoreOptions *ropt;
+ int worker;
+ int pipeRead;
+ int pipeWrite;
+} WorkerInfo;
+
+#define pipewrite(a,b,c) send(a,b,c,0)
+#else
+/*
+ * aborting is only ever used in the master, the workers are fine with just
+ * wantAbort.
+ */
+static bool aborting = false;
+static volatile sig_atomic_t wantAbort = 0;
+
+#define pgpipe(a) pipe(a)
+#define piperead(a,b,c) read(a,b,c)
+#define pipewrite(a,b,c) write(a,b,c)
+#endif
+
+typedef struct ShutdownInformation
+{
+ ParallelState *pstate;
+ Archive *AHX;
+} ShutdownInformation;
+
+static ShutdownInformation shutdown_info;
+
+static const char *modulename = gettext_noop("parallel archiver");
+
+static ParallelSlot *GetMyPSlot(ParallelState *pstate);
+static void
+parallel_msg_master(ParallelSlot *slot, const char *modulename,
+ const char *fmt, va_list ap)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0)));
+static void archive_close_connection(int code, void *arg);
+static void ShutdownWorkersHard(ParallelState *pstate);
+static void WaitForTerminatingWorkers(ParallelState *pstate);
+
+#ifndef WIN32
+static void sigTermHandler(int signum);
+#endif
+static void SetupWorker(ArchiveHandle *AH, int pipefd[2], int worker,
+ RestoreOptions *ropt);
+static bool HasEveryWorkerTerminated(ParallelState *pstate);
+
+static void lockTableNoWait(ArchiveHandle *AH, TocEntry *te);
+static void WaitForCommands(ArchiveHandle *AH, int pipefd[2]);
+static char *getMessageFromMaster(int pipefd[2]);
+static void sendMessageToMaster(int pipefd[2], const char *str);
+static int select_loop(int maxFd, fd_set *workerset);
+static char *getMessageFromWorker(ParallelState *pstate,
+ bool do_wait, int *worker);
+static void sendMessageToWorker(ParallelState *pstate,
+ int worker, const char *str);
+static char *readMessageFromPipe(int fd);
+
+#define messageStartsWith(msg, prefix) \
+ (strncmp(msg, prefix, strlen(prefix)) == 0)
+#define messageEquals(msg, pattern) \
+ (strcmp(msg, pattern) == 0)
+
+#ifdef WIN32
+static void shutdown_parallel_dump_utils(int code, void *unused);
+bool parallel_init_done = false;
+static DWORD tls_index;
+DWORD mainThreadId;
+#endif
+
+
+#ifdef WIN32
+static void
+shutdown_parallel_dump_utils(int code, void *unused)
+{
+ /* Call the cleanup function only from the main thread */
+ if (mainThreadId == GetCurrentThreadId())
+ WSACleanup();
+}
+#endif
+
+void
+init_parallel_dump_utils(void)
+{
+#ifdef WIN32
+ if (!parallel_init_done)
+ {
+ WSADATA wsaData;
+ int err;
+
+ tls_index = TlsAlloc();
+ mainThreadId = GetCurrentThreadId();
+ err = WSAStartup(MAKEWORD(2, 2), &wsaData);
+ if (err != 0)
+ {
+ fprintf(stderr, _("%s: WSAStartup failed: %d\n"), progname, err);
+ exit_nicely(1);
+ }
+ on_exit_nicely(shutdown_parallel_dump_utils, NULL);
+ parallel_init_done = true;
+ }
+#endif
+}
+
+static ParallelSlot *
+GetMyPSlot(ParallelState *pstate)
+{
+ int i;
+
+ for (i = 0; i < pstate->numWorkers; i++)
+#ifdef WIN32
+ if (pstate->parallelSlot[i].threadId == GetCurrentThreadId())
+#else
+ if (pstate->parallelSlot[i].pid == getpid())
+#endif
+ return &(pstate->parallelSlot[i]);
+
+ return NULL;
+}
+
+/*
+ * Fail and die, with a message to stderr. Parameters as for write_msg.
+ *
+ * This is defined in parallel.c, because in parallel mode, things are more
+ * complicated. If the worker process does exit_horribly(), we forward its
+ * last words to the master process. The master process then does
+ * exit_horribly() with this error message itself and prints it normally.
+ * After printing the message, exit_horribly() on the master will shut down
+ * the remaining worker processes.
+ */
+void
+exit_horribly(const char *modulename, const char *fmt,...)
+{
+ va_list ap;
+ ParallelState *pstate = shutdown_info.pstate;
+ ParallelSlot *slot;
+
+ va_start(ap, fmt);
+
+ if (pstate == NULL)
+ {
+ /* Not in parallel mode, just write to stderr */
+ vwrite_msg(modulename, fmt, ap);
+ }
+ else
+ {
+ slot = GetMyPSlot(pstate);
+
+ if (!slot)
+ /* We're the parent, just write the message out */
+ vwrite_msg(modulename, fmt, ap);
+ else
+ /* If we're a worker process, send the msg to the master process */
+ parallel_msg_master(slot, modulename, fmt, ap);
+ }
+
+ va_end(ap);
+
+ exit_nicely(1);
+}
+
+/* Sends the error message from the worker to the master process */
+static void
+parallel_msg_master(ParallelSlot *slot, const char *modulename,
+ const char *fmt, va_list ap)
+{
+ char buf[512];
+ int pipefd[2];
+
+ pipefd[PIPE_READ] = slot->pipeRevRead;
+ pipefd[PIPE_WRITE] = slot->pipeRevWrite;
+
+ strcpy(buf, "ERROR ");
+ vsnprintf(buf + strlen("ERROR "),
+ sizeof(buf) - strlen("ERROR "), fmt, ap);
+
+ sendMessageToMaster(pipefd, buf);
+}
+
+/*
+ * A thread-local version of getLocalPQExpBuffer().
+ *
+ * Non-reentrant but reduces memory leakage. (On Windows the memory leakage
+ * will be one buffer per thread, which is at least better than one per call).
+ */
+static PQExpBuffer
+getThreadLocalPQExpBuffer(void)
+{
+ /*
+ * The Tls code goes awry if we use a static var, so we provide for both
+ * static and auto, and omit any use of the static var when using Tls.
+ */
+ static PQExpBuffer s_id_return = NULL;
+ PQExpBuffer id_return;
+
+#ifdef WIN32
+ if (parallel_init_done)
+ id_return = (PQExpBuffer) TlsGetValue(tls_index); /* 0 when not set */
+ else
+ id_return = s_id_return;
+#else
+ id_return = s_id_return;
+#endif
+
+ if (id_return) /* first time through? */
+ {
+ /* same buffer, just wipe contents */
+ resetPQExpBuffer(id_return);
+ }
+ else
+ {
+ /* new buffer */
+ id_return = createPQExpBuffer();
+#ifdef WIN32
+ if (parallel_init_done)
+ TlsSetValue(tls_index, id_return);
+ else
+ s_id_return = id_return;
+#else
+ s_id_return = id_return;
+#endif
+
+ }
+
+ return id_return;
+}
+
+/*
+ * pg_dump and pg_restore register the Archive pointer for the exit handler
+ * (called from exit_horribly). This function mainly exists so that we can
+ * keep shutdown_info in file scope only.
+ */
+void
+on_exit_close_archive(Archive *AHX)
+{
+ shutdown_info.AHX = AHX;
+ on_exit_nicely(archive_close_connection, &shutdown_info);
+}
+
+/*
+ * This function can close archives in both the parallel and non-parallel
+ * case.
+ */
+static void
+archive_close_connection(int code, void *arg)
+{
+ ShutdownInformation *si = (ShutdownInformation *) arg;
+
+ if (si->pstate)
+ {
+ ParallelSlot *slot = GetMyPSlot(si->pstate);
+
+ if (!slot)
+ {
+ /*
+ * We're the master: We have already printed out the message
+ * passed to exit_horribly() either from the master itself or from
+ * a worker process. Now we need to close our own database
+ * connection (only open during parallel dump but not restore) and
+ * shut down the remaining workers.
+ */
+ DisconnectDatabase(si->AHX);
+#ifndef WIN32
+
+ /*
+ * Setting aborting to true switches to best-effort-mode
+ * (send/receive but ignore errors) in communicating with our
+ * workers.
+ */
+ aborting = true;
+#endif
+ ShutdownWorkersHard(si->pstate);
+ }
+ else if (slot->args->AH)
+ DisconnectDatabase(&(slot->args->AH->public));
+ }
+ else if (si->AHX)
+ DisconnectDatabase(si->AHX);
+}
+
+/*
+ * If we have one worker that terminates for some reason, we'd like the other
+ * threads to terminate as well (and not finish with their 70 GB table dump
+ * first...). Now in UNIX we can just kill these processes, and let the signal
+ * handler set wantAbort to 1. In Windows we set a termEvent and this serves
+ * as the signal for everyone to terminate.
+ */
+void
+checkAborting(ArchiveHandle *AH)
+{
+#ifdef WIN32
+ if (WaitForSingleObject(termEvent, 0) == WAIT_OBJECT_0)
+#else
+ if (wantAbort)
+#endif
+ exit_horribly(modulename, "worker is terminating\n");
+}
+
+/*
+ * Shut down any remaining workers, this has an implicit do_wait == true.
+ *
+ * The fastest way we can make the workers terminate gracefully is when
+ * they are listening for new commands and we just tell them to terminate.
+ */
+static void
+ShutdownWorkersHard(ParallelState *pstate)
+{
+#ifndef WIN32
+ int i;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Close our write end of the sockets so that the workers know they can
+ * exit.
+ */
+ for (i = 0; i < pstate->numWorkers; i++)
+ closesocket(pstate->parallelSlot[i].pipeWrite);
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ kill(pstate->parallelSlot[i].pid, SIGTERM);
+#else
+ /* The workers monitor this event via checkAborting(). */
+ SetEvent(termEvent);
+#endif
+
+ WaitForTerminatingWorkers(pstate);
+}
+
+/*
+ * Wait for the termination of the processes using the OS-specific method.
+ */
+static void
+WaitForTerminatingWorkers(ParallelState *pstate)
+{
+ while (!HasEveryWorkerTerminated(pstate))
+ {
+ ParallelSlot *slot = NULL;
+ int j;
+
+#ifndef WIN32
+ int status;
+ pid_t pid = wait(&status);
+
+ for (j = 0; j < pstate->numWorkers; j++)
+ if (pstate->parallelSlot[j].pid == pid)
+ slot = &(pstate->parallelSlot[j]);
+#else
+ uintptr_t hThread;
+ DWORD ret;
+ uintptr_t *lpHandles = pg_malloc(sizeof(HANDLE) * pstate->numWorkers);
+ int nrun = 0;
+
+ for (j = 0; j < pstate->numWorkers; j++)
+ if (pstate->parallelSlot[j].workerStatus != WRKR_TERMINATED)
+ {
+ lpHandles[nrun] = pstate->parallelSlot[j].hThread;
+ nrun++;
+ }
+ ret = WaitForMultipleObjects(nrun, (HANDLE *) lpHandles, false, INFINITE);
+ Assert(ret != WAIT_FAILED);
+ hThread = lpHandles[ret - WAIT_OBJECT_0];
+
+ for (j = 0; j < pstate->numWorkers; j++)
+ if (pstate->parallelSlot[j].hThread == hThread)
+ slot = &(pstate->parallelSlot[j]);
+
+ free(lpHandles);
+#endif
+ Assert(slot);
+
+ slot->workerStatus = WRKR_TERMINATED;
+ }
+ Assert(HasEveryWorkerTerminated(pstate));
+}
+
+#ifndef WIN32
+/* Signal handling (UNIX only) */
+static void
+sigTermHandler(int signum)
+{
+ wantAbort = 1;
+}
+#endif
+
+/*
+ * This function is called by both UNIX and Windows variants to set up a
+ * worker process.
+ */
+static void
+SetupWorker(ArchiveHandle *AH, int pipefd[2], int worker,
+ RestoreOptions *ropt)
+{
+ /*
+ * Call the setup worker function that's defined in the ArchiveHandle.
+ *
+ * We get the raw connection only for the reason that we can close it
+ * properly when we shut down. This happens only that way when it is
+ * brought down because of an error.
+ */
+ (AH->SetupWorkerPtr) ((Archive *) AH, ropt);
+
+ Assert(AH->connection != NULL);
+
+ WaitForCommands(AH, pipefd);
+
+ closesocket(pipefd[PIPE_READ]);
+ closesocket(pipefd[PIPE_WRITE]);
+}
+
+#ifdef WIN32
+static unsigned __stdcall
+init_spawned_worker_win32(WorkerInfo *wi)
+{
+ ArchiveHandle *AH;
+ int pipefd[2] = {wi->pipeRead, wi->pipeWrite};
+ int worker = wi->worker;
+ RestoreOptions *ropt = wi->ropt;
+
+ AH = CloneArchive(wi->AH);
+
+ free(wi);
+ SetupWorker(AH, pipefd, worker, ropt);
+
+ DeCloneArchive(AH);
+ _endthreadex(0);
+ return 0;
+}
+#endif
+
+/*
+ * This function starts the parallel dump or restore by spawning off the
+ * worker processes in both Unix and Windows. For Windows, it creates a number
+ * of threads while it does a fork() on Unix.
+ */
+ParallelState *
+ParallelBackupStart(ArchiveHandle *AH, RestoreOptions *ropt)
+{
+ ParallelState *pstate;
+ int i;
+ const size_t slotSize = AH->public.numWorkers * sizeof(ParallelSlot);
+
+ Assert(AH->public.numWorkers > 0);
+
+ /* Ensure stdio state is quiesced before forking */
+ fflush(NULL);
+
+ pstate = (ParallelState *) pg_malloc(sizeof(ParallelState));
+
+ pstate->numWorkers = AH->public.numWorkers;
+ pstate->parallelSlot = NULL;
+
+ if (AH->public.numWorkers == 1)
+ return pstate;
+
+ pstate->parallelSlot = (ParallelSlot *) pg_malloc(slotSize);
+ memset((void *) pstate->parallelSlot, 0, slotSize);
+
+ /*
+ * Set the pstate in the shutdown_info. The exit handler uses pstate if
+ * set and falls back to AHX otherwise.
+ */
+ shutdown_info.pstate = pstate;
+ getLocalPQExpBuffer = getThreadLocalPQExpBuffer;
+
+#ifdef WIN32
+ tMasterThreadId = GetCurrentThreadId();
+ termEvent = CreateEvent(NULL, true, false, "Terminate");
+#else
+ signal(SIGTERM, sigTermHandler);
+ signal(SIGINT, sigTermHandler);
+ signal(SIGQUIT, sigTermHandler);
+#endif
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ {
+#ifdef WIN32
+ WorkerInfo *wi;
+ uintptr_t handle;
+#else
+ pid_t pid;
+#endif
+ int pipeMW[2],
+ pipeWM[2];
+
+ if (pgpipe(pipeMW) < 0 || pgpipe(pipeWM) < 0)
+ exit_horribly(modulename,
+ "could not create communication channels: %s\n",
+ strerror(errno));
+
+ pstate->parallelSlot[i].workerStatus = WRKR_IDLE;
+ pstate->parallelSlot[i].args = (ParallelArgs *) pg_malloc(sizeof(ParallelArgs));
+ pstate->parallelSlot[i].args->AH = NULL;
+ pstate->parallelSlot[i].args->te = NULL;
+#ifdef WIN32
+ /* Allocate a new structure for every worker */
+ wi = (WorkerInfo *) pg_malloc(sizeof(WorkerInfo));
+
+ wi->ropt = ropt;
+ wi->worker = i;
+ wi->AH = AH;
+ wi->pipeRead = pstate->parallelSlot[i].pipeRevRead = pipeMW[PIPE_READ];
+ wi->pipeWrite = pstate->parallelSlot[i].pipeRevWrite = pipeWM[PIPE_WRITE];
+
+ handle = _beginthreadex(NULL, 0, (void *) &init_spawned_worker_win32,
+ wi, 0, &(pstate->parallelSlot[i].threadId));
+ pstate->parallelSlot[i].hThread = handle;
+#else
+ pid = fork();
+ if (pid == 0)
+ {
+ /* we are the worker */
+ int j;
+ int pipefd[2];
+
+ pipefd[0] = pipeMW[PIPE_READ];
+ pipefd[1] = pipeWM[PIPE_WRITE];
+
+ /*
+ * Store the fds for the reverse communication in pstate. Actually
+ * we only use this in case of an error and don't use pstate
+ * otherwise in the worker process. On Windows we write to the
+ * global pstate, in Unix we write to our process-local copy but
+ * that's also where we'd retrieve this information back from.
+ */
+ pstate->parallelSlot[i].pipeRevRead = pipefd[PIPE_READ];
+ pstate->parallelSlot[i].pipeRevWrite = pipefd[PIPE_WRITE];
+ pstate->parallelSlot[i].pid = getpid();
+
+ /*
+ * Call CloneArchive on Unix as well even though technically we
+ * don't need to because fork() gives us a copy in our own address
+ * space already. But CloneArchive resets the state information
+ * and also clones the database connection (for parallel dump)
+ * which both seem kinda helpful.
+ */
+ pstate->parallelSlot[i].args->AH = CloneArchive(AH);
+
+ /* close read end of Worker -> Master */
+ closesocket(pipeWM[PIPE_READ]);
+ /* close write end of Master -> Worker */
+ closesocket(pipeMW[PIPE_WRITE]);
+
+ /*
+ * Close all inherited fds for communication of the master with
+ * the other workers.
+ */
+ for (j = 0; j < i; j++)
+ {
+ closesocket(pstate->parallelSlot[j].pipeRead);
+ closesocket(pstate->parallelSlot[j].pipeWrite);
+ }
+
+ SetupWorker(pstate->parallelSlot[i].args->AH, pipefd, i, ropt);
+
+ exit(0);
+ }
+ else if (pid < 0)
+ /* fork failed */
+ exit_horribly(modulename,
+ "could not create worker process: %s\n",
+ strerror(errno));
+
+ /* we are the Master, pid > 0 here */
+ Assert(pid > 0);
+
+ /* close read end of Master -> Worker */
+ closesocket(pipeMW[PIPE_READ]);
+ /* close write end of Worker -> Master */
+ closesocket(pipeWM[PIPE_WRITE]);
+
+ pstate->parallelSlot[i].pid = pid;
+#endif
+
+ pstate->parallelSlot[i].pipeRead = pipeWM[PIPE_READ];
+ pstate->parallelSlot[i].pipeWrite = pipeMW[PIPE_WRITE];
+ }
+
+ return pstate;
+}
+
+/*
+ * Tell all of our workers to terminate.
+ *
+ * Pretty straightforward routine, first we tell everyone to terminate, then
+ * we listen to the workers' replies and finally close the sockets that we
+ * have used for communication.
+ */
+void
+ParallelBackupEnd(ArchiveHandle *AH, ParallelState *pstate)
+{
+ int i;
+
+ if (pstate->numWorkers == 1)
+ return;
+
+ Assert(IsEveryWorkerIdle(pstate));
+
+ /* close the sockets so that the workers know they can exit */
+ for (i = 0; i < pstate->numWorkers; i++)
+ {
+ closesocket(pstate->parallelSlot[i].pipeRead);
+ closesocket(pstate->parallelSlot[i].pipeWrite);
+ }
+ WaitForTerminatingWorkers(pstate);
+
+ /*
+ * Remove the pstate again, so the exit handler in the parent will now
+ * again fall back to closing AH->connection (if connected).
+ */
+ shutdown_info.pstate = NULL;
+
+ free(pstate->parallelSlot);
+ free(pstate);
+}
+
+
+/*
+ * The sequence is the following (for dump, similar for restore):
+ *
+ * The master process starts the parallel backup in ParllelBackupStart, this
+ * forks the worker processes which enter WaitForCommand().
+ *
+ * The master process dispatches an individual work item to one of the worker
+ * processes in DispatchJobForTocEntry(). It calls
+ * AH->MasterStartParallelItemPtr, a routine of the output format. This
+ * function's arguments are the parents archive handle AH (containing the full
+ * catalog information), the TocEntry that the worker should work on and a
+ * T_Action act indicating whether this is a backup or a restore item. The
+ * function then converts the TocEntry assignment into a string that is then
+ * sent over to the worker process. In the simplest case that would be
+ * something like "DUMP 1234", with 1234 being the TocEntry id.
+ *
+ * The worker receives the message in the routine pointed to by
+ * WorkerJobDumpPtr or WorkerJobRestorePtr. These are also pointers to
+ * corresponding routines of the respective output format, e.g.
+ * _WorkerJobDumpDirectory().
+ *
+ * Remember that we have forked off the workers only after we have read in the
+ * catalog. That's why our worker processes can also access the catalog
+ * information. Now they re-translate the textual representation to a TocEntry
+ * on their side and do the required action (restore or dump).
+ *
+ * The result is again a textual string that is sent back to the master and is
+ * interpreted by AH->MasterEndParallelItemPtr. This function can update state
+ * or catalog information on the master's side, depending on the reply from
+ * the worker process. In the end it returns status which is 0 for successful
+ * execution.
+ *
+ * ---------------------------------------------------------------------
+ * Master Worker
+ *
+ * enters WaitForCommands()
+ * DispatchJobForTocEntry(...te...)
+ *
+ * [ Worker is IDLE ]
+ *
+ * arg = (MasterStartParallelItemPtr)()
+ * send: DUMP arg
+ * receive: DUMP arg
+ * str = (WorkerJobDumpPtr)(arg)
+ * [ Worker is WORKING ] ... gets te from arg ...
+ * ... dump te ...
+ * send: OK DUMP info
+ *
+ * In ListenToWorkers():
+ *
+ * [ Worker is FINISHED ]
+ * receive: OK DUMP info
+ * status = (MasterEndParallelItemPtr)(info)
+ *
+ * In ReapWorkerStatus(&ptr):
+ * *ptr = status;
+ * [ Worker is IDLE ]
+ * ---------------------------------------------------------------------
+ */
+void
+DispatchJobForTocEntry(ArchiveHandle *AH, ParallelState *pstate, TocEntry *te,
+ T_Action act)
+{
+ int worker;
+ char *arg;
+
+ /* our caller makes sure that at least one worker is idle */
+ Assert(GetIdleWorker(pstate) != NO_SLOT);
+ worker = GetIdleWorker(pstate);
+ Assert(worker != NO_SLOT);
+
+ arg = (AH->MasterStartParallelItemPtr) (AH, te, act);
+
+ sendMessageToWorker(pstate, worker, arg);
+
+ pstate->parallelSlot[worker].workerStatus = WRKR_WORKING;
+ pstate->parallelSlot[worker].args->te = te;
+}
+
+/*
+ * Find the first free parallel slot (if any).
+ */
+int
+GetIdleWorker(ParallelState *pstate)
+{
+ int i;
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ if (pstate->parallelSlot[i].workerStatus == WRKR_IDLE)
+ return i;
+ return NO_SLOT;
+}
+
+/*
+ * Return true iff every worker process is in the WRKR_TERMINATED state.
+ */
+static bool
+HasEveryWorkerTerminated(ParallelState *pstate)
+{
+ int i;
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ if (pstate->parallelSlot[i].workerStatus != WRKR_TERMINATED)
+ return false;
+ return true;
+}
+
+/*
+ * Return true iff every worker is in the WRKR_IDLE state.
+ */
+bool
+IsEveryWorkerIdle(ParallelState *pstate)
+{
+ int i;
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ if (pstate->parallelSlot[i].workerStatus != WRKR_IDLE)
+ return false;
+ return true;
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * One danger of the parallel backup is a possible deadlock:
+ *
+ * 1) Master dumps the schema and locks all tables in ACCESS SHARE mode.
+ * 2) Another process requests an ACCESS EXCLUSIVE lock (which is not granted
+ * because the master holds a conflicting ACCESS SHARE lock).
+ * 3) The worker process also requests an ACCESS SHARE lock to read the table.
+ * The worker's not granted that lock but is enqueued behind the ACCESS
+ * EXCLUSIVE lock request.
+ * ---------------------------------------------------------------------
+ *
+ * Now what we do here is to just request a lock in ACCESS SHARE but with
+ * NOWAIT in the worker prior to touching the table. If we don't get the lock,
+ * then we know that somebody else has requested an ACCESS EXCLUSIVE lock and
+ * are good to just fail the whole backup because we have detected a deadlock.
+ */
+static void
+lockTableNoWait(ArchiveHandle *AH, TocEntry *te)
+{
+ Archive *AHX = (Archive *) AH;
+ const char *qualId;
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+
+ Assert(AH->format == archDirectory);
+ Assert(strcmp(te->desc, "BLOBS") != 0);
+
+ appendPQExpBuffer(query,
+ "SELECT pg_namespace.nspname,"
+ " pg_class.relname "
+ " FROM pg_class "
+ " JOIN pg_namespace on pg_namespace.oid = relnamespace "
+ " WHERE pg_class.oid = %u", te->catalogId.oid);
+
+ res = PQexec(AH->connection, query->data);
+
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ exit_horribly(modulename,
+ "could not get relation name for OID %u: %s\n",
+ te->catalogId.oid, PQerrorMessage(AH->connection));
+
+ resetPQExpBuffer(query);
+
+ qualId = fmtQualifiedId(AHX->remoteVersion,
+ PQgetvalue(res, 0, 0),
+ PQgetvalue(res, 0, 1));
+
+ appendPQExpBuffer(query, "LOCK TABLE %s IN ACCESS SHARE MODE NOWAIT",
+ qualId);
+ PQclear(res);
+
+ res = PQexec(AH->connection, query->data);
+
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+ exit_horribly(modulename,
+ "could not obtain lock on relation \"%s\"\n"
+ "This usually means that someone requested an ACCESS EXCLUSIVE lock "
+ "on the table after the pg_dump parent process had gotten the "
+ "initial ACCESS SHARE lock on the table.\n", qualId);
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * That's the main routine for the worker.
+ * When it starts up it enters this routine and waits for commands from the
+ * master process. After having processed a command it comes back to here to
+ * wait for the next command. Finally it will receive a TERMINATE command and
+ * exit.
+ */
+static void
+WaitForCommands(ArchiveHandle *AH, int pipefd[2])
+{
+ char *command;
+ DumpId dumpId;
+ int nBytes;
+ char *str = NULL;
+ TocEntry *te;
+
+ for (;;)
+ {
+ if (!(command = getMessageFromMaster(pipefd)))
+ {
+ PQfinish(AH->connection);
+ AH->connection = NULL;
+ return;
+ }
+
+ if (messageStartsWith(command, "DUMP "))
+ {
+ Assert(AH->format == archDirectory);
+ sscanf(command + strlen("DUMP "), "%d%n", &dumpId, &nBytes);
+ Assert(nBytes == strlen(command) - strlen("DUMP "));
+
+ te = getTocEntryByDumpId(AH, dumpId);
+ Assert(te != NULL);
+
+ /*
+ * Lock the table but with NOWAIT. Note that the parent is already
+ * holding a lock. If we cannot acquire another ACCESS SHARE MODE
+ * lock, then somebody else has requested an exclusive lock in the
+ * meantime. lockTableNoWait dies in this case to prevent a
+ * deadlock.
+ */
+ if (strcmp(te->desc, "BLOBS") != 0)
+ lockTableNoWait(AH, te);
+
+ /*
+ * The message we return here has been pg_malloc()ed and we are
+ * responsible for free()ing it.
+ */
+ str = (AH->WorkerJobDumpPtr) (AH, te);
+ Assert(AH->connection != NULL);
+ sendMessageToMaster(pipefd, str);
+ free(str);
+ }
+ else if (messageStartsWith(command, "RESTORE "))
+ {
+ Assert(AH->format == archDirectory || AH->format == archCustom);
+ Assert(AH->connection != NULL);
+
+ sscanf(command + strlen("RESTORE "), "%d%n", &dumpId, &nBytes);
+ Assert(nBytes == strlen(command) - strlen("RESTORE "));
+
+ te = getTocEntryByDumpId(AH, dumpId);
+ Assert(te != NULL);
+
+ /*
+ * The message we return here has been pg_malloc()ed and we are
+ * responsible for free()ing it.
+ */
+ str = (AH->WorkerJobRestorePtr) (AH, te);
+ Assert(AH->connection != NULL);
+ sendMessageToMaster(pipefd, str);
+ free(str);
+ }
+ else
+ exit_horribly(modulename,
+ "unrecognized command on communication channel: %s\n",
+ command);
+
+ /* command was pg_malloc'd and we are responsible for free()ing it. */
+ free(command);
+ }
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * Note the status change:
+ *
+ * DispatchJobForTocEntry WRKR_IDLE -> WRKR_WORKING
+ * ListenToWorkers WRKR_WORKING -> WRKR_FINISHED / WRKR_TERMINATED
+ * ReapWorkerStatus WRKR_FINISHED -> WRKR_IDLE
+ * ---------------------------------------------------------------------
+ *
+ * Just calling ReapWorkerStatus() when all workers are working might or might
+ * not give you an idle worker because you need to call ListenToWorkers() in
+ * between and only thereafter ReapWorkerStatus(). This is necessary in order
+ * to get and deal with the status (=result) of the worker's execution.
+ */
+void
+ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait)
+{
+ int worker;
+ char *msg;
+
+ msg = getMessageFromWorker(pstate, do_wait, &worker);
+
+ if (!msg)
+ {
+ if (do_wait)
+ exit_horribly(modulename, "a worker process died unexpectedly\n");
+ return;
+ }
+
+ if (messageStartsWith(msg, "OK "))
+ {
+ char *statusString;
+ TocEntry *te;
+
+ pstate->parallelSlot[worker].workerStatus = WRKR_FINISHED;
+ te = pstate->parallelSlot[worker].args->te;
+ if (messageStartsWith(msg, "OK RESTORE "))
+ {
+ statusString = msg + strlen("OK RESTORE ");
+ pstate->parallelSlot[worker].status =
+ (AH->MasterEndParallelItemPtr)
+ (AH, te, statusString, ACT_RESTORE);
+ }
+ else if (messageStartsWith(msg, "OK DUMP "))
+ {
+ statusString = msg + strlen("OK DUMP ");
+ pstate->parallelSlot[worker].status =
+ (AH->MasterEndParallelItemPtr)
+ (AH, te, statusString, ACT_DUMP);
+ }
+ else
+ exit_horribly(modulename,
+ "invalid message received from worker: %s\n", msg);
+ }
+ else if (messageStartsWith(msg, "ERROR "))
+ {
+ Assert(AH->format == archDirectory || AH->format == archCustom);
+ pstate->parallelSlot[worker].workerStatus = WRKR_TERMINATED;
+ exit_horribly(modulename, "%s", msg + strlen("ERROR "));
+ }
+ else
+ exit_horribly(modulename, "invalid message received from worker: %s\n", msg);
+
+ /* both Unix and Win32 return pg_malloc()ed space, so we free it */
+ free(msg);
+}
+
+/*
+ * This function is executed in the master process.
+ *
+ * This function is used to get the return value of a terminated worker
+ * process. If a process has terminated, its status is stored in *status and
+ * the id of the worker is returned.
+ */
+int
+ReapWorkerStatus(ParallelState *pstate, int *status)
+{
+ int i;
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ {
+ if (pstate->parallelSlot[i].workerStatus == WRKR_FINISHED)
+ {
+ *status = pstate->parallelSlot[i].status;
+ pstate->parallelSlot[i].status = 0;
+ pstate->parallelSlot[i].workerStatus = WRKR_IDLE;
+ return i;
+ }
+ }
+ return NO_SLOT;
+}
+
+/*
+ * This function is executed in the master process.
+ *
+ * It looks for an idle worker process and only returns if there is one.
+ */
+void
+EnsureIdleWorker(ArchiveHandle *AH, ParallelState *pstate)
+{
+ int ret_worker;
+ int work_status;
+
+ for (;;)
+ {
+ int nTerm = 0;
+
+ while ((ret_worker = ReapWorkerStatus(pstate, &work_status)) != NO_SLOT)
+ {
+ if (work_status != 0)
+ exit_horribly(modulename, "error processing a parallel work item\n");
+
+ nTerm++;
+ }
+
+ /*
+ * We need to make sure that we have an idle worker before dispatching
+ * the next item. If nTerm > 0 we already have that (quick check).
+ */
+ if (nTerm > 0)
+ return;
+
+ /* explicit check for an idle worker */
+ if (GetIdleWorker(pstate) != NO_SLOT)
+ return;
+
+ /*
+ * If we have no idle worker, read the result of one or more workers
+ * and loop the loop to call ReapWorkerStatus() on them
+ */
+ ListenToWorkers(AH, pstate, true);
+ }
+}
+
+/*
+ * This function is executed in the master process.
+ *
+ * It waits for all workers to terminate.
+ */
+void
+EnsureWorkersFinished(ArchiveHandle *AH, ParallelState *pstate)
+{
+ int work_status;
+
+ if (!pstate || pstate->numWorkers == 1)
+ return;
+
+ /* Waiting for the remaining worker processes to finish */
+ while (!IsEveryWorkerIdle(pstate))
+ {
+ if (ReapWorkerStatus(pstate, &work_status) == NO_SLOT)
+ ListenToWorkers(AH, pstate, true);
+ else if (work_status != 0)
+ exit_horribly(modulename,
+ "error processing a parallel work item\n");
+ }
+}
+
+/*
+ * This function is executed in the worker process.
+ *
+ * It returns the next message on the communication channel, blocking until it
+ * becomes available.
+ */
+static char *
+getMessageFromMaster(int pipefd[2])
+{
+ return readMessageFromPipe(pipefd[PIPE_READ]);
+}
+
+/*
+ * This function is executed in the worker process.
+ *
+ * It sends a message to the master on the communication channel.
+ */
+static void
+sendMessageToMaster(int pipefd[2], const char *str)
+{
+ int len = strlen(str) + 1;
+
+ if (pipewrite(pipefd[PIPE_WRITE], str, len) != len)
+ exit_horribly(modulename,
+ "could not write to the communication channel: %s\n",
+ strerror(errno));
+}
+
+/*
+ * A select loop that repeats calling select until a descriptor in the read
+ * set becomes readable. On Windows we have to check for the termination event
+ * from time to time, on Unix we can just block forever.
+ */
+static int
+select_loop(int maxFd, fd_set *workerset)
+{
+ int i;
+ fd_set saveSet = *workerset;
+
+#ifdef WIN32
+ /* should always be the master */
+ Assert(tMasterThreadId == GetCurrentThreadId());
+
+ for (;;)
+ {
+ /*
+ * sleep a quarter of a second before checking if we should terminate.
+ */
+ struct timeval tv = {0, 250000};
+
+ *workerset = saveSet;
+ i = select(maxFd + 1, workerset, NULL, NULL, &tv);
+
+ if (i == SOCKET_ERROR && WSAGetLastError() == WSAEINTR)
+ continue;
+ if (i)
+ break;
+ }
+#else /* UNIX */
+
+ for (;;)
+ {
+ *workerset = saveSet;
+ i = select(maxFd + 1, workerset, NULL, NULL, NULL);
+
+ /*
+ * If we Ctrl-C the master process , it's likely that we interrupt
+ * select() here. The signal handler will set wantAbort == true and
+ * the shutdown journey starts from here. Note that we'll come back
+ * here later when we tell all workers to terminate and read their
+ * responses. But then we have aborting set to true.
+ */
+ if (wantAbort && !aborting)
+ exit_horribly(modulename, "terminated by user\n");
+
+ if (i < 0 && errno == EINTR)
+ continue;
+ break;
+ }
+#endif
+
+ return i;
+}
+
+
+/*
+ * This function is executed in the master process.
+ *
+ * It returns the next message from the worker on the communication channel,
+ * optionally blocking (do_wait) until it becomes available.
+ *
+ * The id of the worker is returned in *worker.
+ */
+static char *
+getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker)
+{
+ int i;
+ fd_set workerset;
+ int maxFd = -1;
+ struct timeval nowait = {0, 0};
+
+ FD_ZERO(&workerset);
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ {
+ if (pstate->parallelSlot[i].workerStatus == WRKR_TERMINATED)
+ continue;
+ FD_SET(pstate->parallelSlot[i].pipeRead, &workerset);
+ /* actually WIN32 ignores the first parameter to select()... */
+ if (pstate->parallelSlot[i].pipeRead > maxFd)
+ maxFd = pstate->parallelSlot[i].pipeRead;
+ }
+
+ if (do_wait)
+ {
+ i = select_loop(maxFd, &workerset);
+ Assert(i != 0);
+ }
+ else
+ {
+ if ((i = select(maxFd + 1, &workerset, NULL, NULL, &nowait)) == 0)
+ return NULL;
+ }
+
+ if (i < 0)
+ exit_horribly(modulename, "error in ListenToWorkers(): %s\n", strerror(errno));
+
+ for (i = 0; i < pstate->numWorkers; i++)
+ {
+ char *msg;
+
+ if (!FD_ISSET(pstate->parallelSlot[i].pipeRead, &workerset))
+ continue;
+
+ msg = readMessageFromPipe(pstate->parallelSlot[i].pipeRead);
+ *worker = i;
+ return msg;
+ }
+ Assert(false);
+ return NULL;
+}
+
+/*
+ * This function is executed in the master process.
+ *
+ * It sends a message to a certain worker on the communication channel.
+ */
+static void
+sendMessageToWorker(ParallelState *pstate, int worker, const char *str)
+{
+ int len = strlen(str) + 1;
+
+ if (pipewrite(pstate->parallelSlot[worker].pipeWrite, str, len) != len)
+ {
+ /*
+ * If we're already aborting anyway, don't care if we succeed or not.
+ * The child might have gone already.
+ */
+#ifndef WIN32
+ if (!aborting)
+#endif
+ exit_horribly(modulename,
+ "could not write to the communication channel: %s\n",
+ strerror(errno));
+ }
+}
+
+/*
+ * The underlying function to read a message from the communication channel
+ * (fd) with optional blocking (do_wait).
+ */
+static char *
+readMessageFromPipe(int fd)
+{
+ char *msg;
+ int msgsize,
+ bufsize;
+ int ret;
+
+ /*
+ * The problem here is that we need to deal with several possibilites: we
+ * could receive only a partial message or several messages at once. The
+ * caller expects us to return exactly one message however.
+ *
+ * We could either read in as much as we can and keep track of what we
+ * delivered back to the caller or we just read byte by byte. Once we see
+ * (char) 0, we know that it's the message's end. This would be quite
+ * inefficient for more data but since we are reading only on the command
+ * channel, the performance loss does not seem worth the trouble of
+ * keeping internal states for different file descriptors.
+ */
+ bufsize = 64; /* could be any number */
+ msg = (char *) pg_malloc(bufsize);
+
+ msgsize = 0;
+ for (;;)
+ {
+ Assert(msgsize <= bufsize);
+ ret = piperead(fd, msg + msgsize, 1);
+
+ /* worker has closed the connection or another error happened */
+ if (ret <= 0)
+ break;
+
+ Assert(ret == 1);
+
+ if (msg[msgsize] == '\0')
+ return msg;
+
+ msgsize++;
+ if (msgsize == bufsize)
+ {
+ /* could be any number */
+ bufsize += 16;
+ msg = (char *) pg_realloc(msg, bufsize);
+ }
+ }
+
+ /*
+ * Worker has closed the connection, make sure to clean up before return
+ * since we are not returning msg (but did allocate it).
+ */
+ pg_free(msg);
+
+ return NULL;
+}
+
+#ifdef WIN32
+/*
+ * This is a replacement version of pipe for Win32 which allows returned
+ * handles to be used in select(). Note that read/write calls must be replaced
+ * with recv/send. "handles" have to be integers so we check for errors then
+ * cast to integers.
+ */
+static int
+pgpipe(int handles[2])
+{
+ pgsocket s, tmp_sock;
+ struct sockaddr_in serv_addr;
+ int len = sizeof(serv_addr);
+
+ /* We have to use the Unix socket invalid file descriptor value here. */
+ handles[0] = handles[1] = -1;
+
+ /*
+ * setup listen socket
+ */
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
+ {
+ write_msg(modulename, "pgpipe: could not create socket: error code %d\n",
+ WSAGetLastError());
+ return -1;
+ }
+
+ memset((void *) &serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_port = htons(0);
+ serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(s, (SOCKADDR *) &serv_addr, len) == SOCKET_ERROR)
+ {
+ write_msg(modulename, "pgpipe: could not bind: error code %d\n",
+ WSAGetLastError());
+ closesocket(s);
+ return -1;
+ }
+ if (listen(s, 1) == SOCKET_ERROR)
+ {
+ write_msg(modulename, "pgpipe: could not listen: error code %d\n",
+ WSAGetLastError());
+ closesocket(s);
+ return -1;
+ }
+ if (getsockname(s, (SOCKADDR *) &serv_addr, &len) == SOCKET_ERROR)
+ {
+ write_msg(modulename, "pgpipe: getsockname() failed: error code %d\n",
+ WSAGetLastError());
+ closesocket(s);
+ return -1;
+ }
+
+ /*
+ * setup pipe handles
+ */
+ if ((tmp_sock = socket(AF_INET, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
+ {
+ write_msg(modulename, "pgpipe: could not create second socket: error code %d\n",
+ WSAGetLastError());
+ closesocket(s);
+ return -1;
+ }
+ handles[1] = (int) tmp_sock;
+
+ if (connect(handles[1], (SOCKADDR *) &serv_addr, len) == SOCKET_ERROR)
+ {
+ write_msg(modulename, "pgpipe: could not connect socket: error code %d\n",
+ WSAGetLastError());
+ closesocket(s);
+ return -1;
+ }
+ if ((tmp_sock = accept(s, (SOCKADDR *) &serv_addr, &len)) == PGINVALID_SOCKET)
+ {
+ write_msg(modulename, "pgpipe: could not accept connection: error code %d\n",
+ WSAGetLastError());
+ closesocket(handles[1]);
+ handles[1] = -1;
+ closesocket(s);
+ return -1;
+ }
+ handles[0] = (int) tmp_sock;
+
+ closesocket(s);
+ return 0;
+}
+
+static int
+piperead(int s, char *buf, int len)
+{
+ int ret = recv(s, buf, len, 0);
+
+ if (ret < 0 && WSAGetLastError() == WSAECONNRESET)
+ /* EOF on the pipe! (win32 socket based implementation) */
+ ret = 0;
+ return ret;
+}
+
+#endif
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/parallel.h
@@ -0,0 +1,95 @@
+/*-------------------------------------------------------------------------
+ *
+ * parallel.h
+ *
+ * Parallel support header file for the pg_dump archiver
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * The author is not responsible for loss or damages that may
+ * result from its use.
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/parallel.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PG_DUMP_PARALLEL_H
+#define PG_DUMP_PARALLEL_H
+
+#include "pg_backup_db.h"
+
+struct _archiveHandle;
+struct _tocEntry;
+
+typedef enum
+{
+ WRKR_TERMINATED = 0,
+ WRKR_IDLE,
+ WRKR_WORKING,
+ WRKR_FINISHED
+} T_WorkerStatus;
+
+/* Arguments needed for a worker process */
+typedef struct ParallelArgs
+{
+ struct _archiveHandle *AH;
+ struct _tocEntry *te;
+} ParallelArgs;
+
+/* State for each parallel activity slot */
+typedef struct ParallelSlot
+{
+ ParallelArgs *args;
+ T_WorkerStatus workerStatus;
+ int status;
+ int pipeRead;
+ int pipeWrite;
+ int pipeRevRead;
+ int pipeRevWrite;
+#ifdef WIN32
+ uintptr_t hThread;
+ unsigned int threadId;
+#else
+ pid_t pid;
+#endif
+} ParallelSlot;
+
+#define NO_SLOT (-1)
+
+typedef struct ParallelState
+{
+ int numWorkers;
+ ParallelSlot *parallelSlot;
+} ParallelState;
+
+#ifdef WIN32
+extern bool parallel_init_done;
+extern DWORD mainThreadId;
+#endif
+
+extern void init_parallel_dump_utils(void);
+
+extern int GetIdleWorker(ParallelState *pstate);
+extern bool IsEveryWorkerIdle(ParallelState *pstate);
+extern void ListenToWorkers(struct _archiveHandle * AH, ParallelState *pstate, bool do_wait);
+extern int ReapWorkerStatus(ParallelState *pstate, int *status);
+extern void EnsureIdleWorker(struct _archiveHandle * AH, ParallelState *pstate);
+extern void EnsureWorkersFinished(struct _archiveHandle * AH, ParallelState *pstate);
+
+extern ParallelState *ParallelBackupStart(struct _archiveHandle * AH,
+ RestoreOptions *ropt);
+extern void DispatchJobForTocEntry(struct _archiveHandle * AH,
+ ParallelState *pstate,
+ struct _tocEntry * te, T_Action act);
+extern void ParallelBackupEnd(struct _archiveHandle * AH, ParallelState *pstate);
+
+extern void checkAborting(struct _archiveHandle * AH);
+
+extern void
+exit_horribly(const char *modulename, const char *fmt,...)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3), noreturn));
+
+#endif /* PG_DUMP_PARALLEL_H */
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup.h
@@ -0,0 +1,220 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup.h
+ *
+ * Public interface to the pg_dump archiver routines.
+ *
+ * See the headers to pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PG_BACKUP_H
+#define PG_BACKUP_H
+
+#include "postgres_fe.h"
+
+#include "pg_dump.h"
+#include "dumputils.h"
+
+#include "libpq-fe.h"
+
+
+#define atooid(x) ((Oid) strtoul((x), NULL, 10))
+#define oidcmp(x,y) ( ((x) < (y) ? -1 : ((x) > (y)) ? 1 : 0) )
+#define oideq(x,y) ( (x) == (y) )
+#define oidle(x,y) ( (x) <= (y) )
+#define oidge(x,y) ( (x) >= (y) )
+#define oidzero(x) ( (x) == 0 )
+
+enum trivalue
+{
+ TRI_DEFAULT,
+ TRI_NO,
+ TRI_YES
+};
+
+typedef enum _archiveFormat
+{
+ archUnknown = 0,
+ archCustom = 1,
+ archTar = 3,
+ archNull = 4,
+ archDirectory = 5
+} ArchiveFormat;
+
+typedef enum _archiveMode
+{
+ archModeAppend,
+ archModeWrite,
+ archModeRead
+} ArchiveMode;
+
+typedef enum _teSection
+{
+ SECTION_NONE = 1, /* COMMENTs, ACLs, etc; can be anywhere */
+ SECTION_PRE_DATA, /* stuff to be processed before data */
+ SECTION_DATA, /* TABLE DATA, BLOBS, BLOB COMMENTS */
+ SECTION_POST_DATA /* stuff to be processed after data */
+} teSection;
+
+/*
+ * We may want to have some more user-readable data, but in the mean
+ * time this gives us some abstraction and type checking.
+ */
+struct Archive
+{
+ int verbose;
+ char *remoteVersionStr; /* server's version string */
+ int remoteVersion; /* same in numeric form */
+
+ int minRemoteVersion; /* allowable range */
+ int maxRemoteVersion;
+
+ int numWorkers; /* number of parallel processes */
+ char *sync_snapshot_id; /* sync snapshot id for parallel
+ * operation */
+
+ /* info needed for string escaping */
+ int encoding; /* libpq code for client_encoding */
+ bool std_strings; /* standard_conforming_strings */
+ char *use_role; /* Issue SET ROLE to this */
+
+ /* error handling */
+ bool exit_on_error; /* whether to exit on SQL errors... */
+ int n_errors; /* number of errors (if no die) */
+
+ /* The rest is private */
+};
+
+typedef int (*DataDumperPtr) (Archive *AH, void *userArg);
+
+typedef struct _restoreOptions
+{
+ int createDB; /* Issue commands to create the database */
+ int noOwner; /* Don't try to match original object owner */
+ int noTablespace; /* Don't issue tablespace-related commands */
+ int disable_triggers; /* disable triggers during data-only
+ * restore */
+ int use_setsessauth;/* Use SET SESSION AUTHORIZATION commands
+ * instead of OWNER TO */
+ int no_security_labels; /* Skip security label entries */
+ char *superuser; /* Username to use as superuser */
+ char *use_role; /* Issue SET ROLE to this */
+ int dropSchema;
+ int if_exists;
+ const char *filename;
+ int dataOnly;
+ int schemaOnly;
+ int dumpSections;
+ int verbose;
+ int aclsSkip;
+ int tocSummary;
+ char *tocFile;
+ int format;
+ char *formatName;
+
+ int selTypes;
+ int selIndex;
+ int selFunction;
+ int selTrigger;
+ int selTable;
+ SimpleStringList indexNames;
+ SimpleStringList functionNames;
+ SimpleStringList schemaNames;
+ SimpleStringList triggerNames;
+ SimpleStringList tableNames;
+
+ int useDB;
+ char *dbname;
+ char *pgport;
+ char *pghost;
+ char *username;
+ int noDataForFailedTables;
+ enum trivalue promptPassword;
+ int exit_on_error;
+ int compression;
+ int suppressDumpWarnings; /* Suppress output of WARNING entries
+ * to stderr */
+ bool single_txn;
+
+ bool *idWanted; /* array showing which dump IDs to emit */
+} RestoreOptions;
+
+typedef void (*SetupWorkerPtr) (Archive *AH, RestoreOptions *ropt);
+
+/*
+ * Main archiver interface.
+ */
+
+extern void ConnectDatabase(Archive *AH,
+ const char *dbname,
+ const char *pghost,
+ const char *pgport,
+ const char *username,
+ enum trivalue prompt_password);
+extern void DisconnectDatabase(Archive *AHX);
+extern PGconn *GetConnection(Archive *AHX);
+
+/* Called to add a TOC entry */
+extern void ArchiveEntry(Archive *AHX,
+ CatalogId catalogId, DumpId dumpId,
+ const char *tag,
+ const char *namespace, const char *tablespace,
+ const char *owner, bool withOids,
+ const char *desc, teSection section,
+ const char *defn,
+ const char *dropStmt, const char *copyStmt,
+ const DumpId *deps, int nDeps,
+ DataDumperPtr dumpFn, void *dumpArg);
+
+/* Called to write *data* to the archive */
+extern void WriteData(Archive *AH, const void *data, size_t dLen);
+
+extern int StartBlob(Archive *AH, Oid oid);
+extern int EndBlob(Archive *AH, Oid oid);
+
+extern void CloseArchive(Archive *AH);
+
+extern void SetArchiveRestoreOptions(Archive *AH, RestoreOptions *ropt);
+
+extern void RestoreArchive(Archive *AH);
+
+/* Open an existing archive */
+extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
+
+/* Create a new archive */
+extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
+ const int compression, ArchiveMode mode,
+ SetupWorkerPtr setupDumpWorker);
+
+/* The --list option */
+extern void PrintTOCSummary(Archive *AH, RestoreOptions *ropt);
+
+extern RestoreOptions *NewRestoreOptions(void);
+
+/* Rearrange and filter TOC entries */
+extern void SortTocFromFile(Archive *AHX, RestoreOptions *ropt);
+
+/* Convenience functions used only when writing DATA */
+extern void archputs(const char *s, Archive *AH);
+extern int
+archprintf(Archive *AH, const char *fmt,...)
+/* This extension allows gcc to check the format string */
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+
+#define appendStringLiteralAH(buf,str,AH) \
+ appendStringLiteral(buf, str, (AH)->encoding, (AH)->std_strings)
+
+#endif /* PG_BACKUP_H */
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_archiver.c
@@ -0,0 +1,4361 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_archiver.c
+ *
+ * Private implementation of the archiver routines.
+ *
+ * See the headers to pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from its use.
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_archiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "pg_backup_db.h"
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#ifdef WIN32
+#include <io.h>
+#endif
+
+#include "libpq/libpq-fs.h"
+
+#define TEXT_DUMP_HEADER "--\n-- PostgreSQL database dump\n--\n\n"
+#define TEXT_DUMPALL_HEADER "--\n-- PostgreSQL database cluster dump\n--\n\n"
+
+/* state needed to save/restore an archive's output target */
+typedef struct _outputContext
+{
+ void *OF;
+ int gzOut;
+} OutputContext;
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("archiver");
+
+
+static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
+ const int compression, ArchiveMode mode, SetupWorkerPtr setupWorkerPtr);
+static void _getObjectDescription(PQExpBuffer buf, TocEntry *te,
+ ArchiveHandle *AH);
+static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isData, bool acl_pass);
+static char *replace_line_endings(const char *str);
+static void _doSetFixedOutputState(ArchiveHandle *AH);
+static void _doSetSessionAuth(ArchiveHandle *AH, const char *user);
+static void _doSetWithOids(ArchiveHandle *AH, const bool withOids);
+static void _reconnectToDB(ArchiveHandle *AH, const char *dbname);
+static void _becomeUser(ArchiveHandle *AH, const char *user);
+static void _becomeOwner(ArchiveHandle *AH, TocEntry *te);
+static void _selectOutputSchema(ArchiveHandle *AH, const char *schemaName);
+static void _selectTablespace(ArchiveHandle *AH, const char *tablespace);
+static void processEncodingEntry(ArchiveHandle *AH, TocEntry *te);
+static void processStdStringsEntry(ArchiveHandle *AH, TocEntry *te);
+static teReqs _tocEntryRequired(TocEntry *te, teSection curSection, RestoreOptions *ropt);
+static bool _tocEntryIsACL(TocEntry *te);
+static void _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt);
+static void _enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt);
+static void buildTocEntryArrays(ArchiveHandle *AH);
+static void _moveBefore(ArchiveHandle *AH, TocEntry *pos, TocEntry *te);
+static int _discoverArchiveFormat(ArchiveHandle *AH);
+
+static int RestoringToDB(ArchiveHandle *AH);
+static void dump_lo_buf(ArchiveHandle *AH);
+static void dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim);
+static void SetOutput(ArchiveHandle *AH, const char *filename, int compression);
+static OutputContext SaveOutput(ArchiveHandle *AH);
+static void RestoreOutput(ArchiveHandle *AH, OutputContext savedContext);
+
+static int restore_toc_entry(ArchiveHandle *AH, TocEntry *te,
+ RestoreOptions *ropt, bool is_parallel);
+static void restore_toc_entries_prefork(ArchiveHandle *AH);
+static void restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate,
+ TocEntry *pending_list);
+static void restore_toc_entries_postfork(ArchiveHandle *AH, TocEntry *pending_list);
+static void par_list_header_init(TocEntry *l);
+static void par_list_append(TocEntry *l, TocEntry *te);
+static void par_list_remove(TocEntry *te);
+static TocEntry *get_next_work_item(ArchiveHandle *AH,
+ TocEntry *ready_list,
+ ParallelState *pstate);
+static void mark_work_done(ArchiveHandle *AH, TocEntry *ready_list,
+ int worker, int status,
+ ParallelState *pstate);
+static void fix_dependencies(ArchiveHandle *AH);
+static bool has_lock_conflicts(TocEntry *te1, TocEntry *te2);
+static void repoint_table_dependencies(ArchiveHandle *AH);
+static void identify_locking_dependencies(ArchiveHandle *AH, TocEntry *te);
+static void reduce_dependencies(ArchiveHandle *AH, TocEntry *te,
+ TocEntry *ready_list);
+static void mark_create_done(ArchiveHandle *AH, TocEntry *te);
+static void inhibit_data_for_failed_table(ArchiveHandle *AH, TocEntry *te);
+
+/*
+ * Wrapper functions.
+ *
+ * The objective it to make writing new formats and dumpers as simple
+ * as possible, if necessary at the expense of extra function calls etc.
+ *
+ */
+
+/*
+ * The dump worker setup needs lots of knowledge of the internals of pg_dump,
+ * so It's defined in pg_dump.c and passed into OpenArchive. The restore worker
+ * setup doesn't need to know anything much, so it's defined here.
+ */
+static void
+setupRestoreWorker(Archive *AHX, RestoreOptions *ropt)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+
+ (AH->ReopenPtr) (AH);
+}
+
+
+/* Create a new archive */
+/* Public */
+Archive *
+CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
+ const int compression, ArchiveMode mode, SetupWorkerPtr setupDumpWorker)
+
+{
+ ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression, mode, setupDumpWorker);
+
+ return (Archive *) AH;
+}
+
+/* Open an existing archive */
+/* Public */
+Archive *
+OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
+{
+ ArchiveHandle *AH = _allocAH(FileSpec, fmt, 0, archModeRead, setupRestoreWorker);
+
+ return (Archive *) AH;
+}
+
+/* Public */
+void
+CloseArchive(Archive *AHX)
+{
+ int res = 0;
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+
+ (*AH->ClosePtr) (AH);
+
+ /* Close the output */
+ if (AH->gzOut)
+ res = GZCLOSE(AH->OF);
+ else if (AH->OF != stdout)
+ res = fclose(AH->OF);
+
+ if (res != 0)
+ exit_horribly(modulename, "could not close output file: %s\n",
+ strerror(errno));
+}
+
+/* Public */
+void
+SetArchiveRestoreOptions(Archive *AHX, RestoreOptions *ropt)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ TocEntry *te;
+ teSection curSection;
+
+ /* Save options for later access */
+ AH->ropt = ropt;
+
+ /* Decide which TOC entries will be dumped/restored, and mark them */
+ curSection = SECTION_PRE_DATA;
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ /*
+ * When writing an archive, we also take this opportunity to check
+ * that we have generated the entries in a sane order that respects
+ * the section divisions. When reading, don't complain, since buggy
+ * old versions of pg_dump might generate out-of-order archives.
+ */
+ if (AH->mode != archModeRead)
+ {
+ switch (te->section)
+ {
+ case SECTION_NONE:
+ /* ok to be anywhere */
+ break;
+ case SECTION_PRE_DATA:
+ if (curSection != SECTION_PRE_DATA)
+ write_msg(modulename,
+ "WARNING: archive items not in correct section order\n");
+ break;
+ case SECTION_DATA:
+ if (curSection == SECTION_POST_DATA)
+ write_msg(modulename,
+ "WARNING: archive items not in correct section order\n");
+ break;
+ case SECTION_POST_DATA:
+ /* ok no matter which section we were in */
+ break;
+ default:
+ exit_horribly(modulename, "unexpected section code %d\n",
+ (int) te->section);
+ break;
+ }
+ }
+
+ if (te->section != SECTION_NONE)
+ curSection = te->section;
+
+ te->reqs = _tocEntryRequired(te, curSection, ropt);
+ }
+}
+
+/* Public */
+void
+RestoreArchive(Archive *AHX)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ RestoreOptions *ropt = AH->ropt;
+ bool parallel_mode;
+ TocEntry *te;
+ OutputContext sav;
+
+ AH->stage = STAGE_INITIALIZING;
+
+ /*
+ * Check for nonsensical option combinations.
+ *
+ * -C is not compatible with -1, because we can't create a database inside
+ * a transaction block.
+ */
+ if (ropt->createDB && ropt->single_txn)
+ exit_horribly(modulename, "-C and -1 are incompatible options\n");
+
+ /*
+ * If we're going to do parallel restore, there are some restrictions.
+ */
+ parallel_mode = (AH->public.numWorkers > 1 && ropt->useDB);
+ if (parallel_mode)
+ {
+ /* We haven't got round to making this work for all archive formats */
+ if (AH->ClonePtr == NULL || AH->ReopenPtr == NULL)
+ exit_horribly(modulename, "parallel restore is not supported with this archive file format\n");
+
+ /* Doesn't work if the archive represents dependencies as OIDs */
+ if (AH->version < K_VERS_1_8)
+ exit_horribly(modulename, "parallel restore is not supported with archives made by pre-8.0 pg_dump\n");
+
+ /*
+ * It's also not gonna work if we can't reopen the input file, so
+ * let's try that immediately.
+ */
+ (AH->ReopenPtr) (AH);
+ }
+
+ /*
+ * Make sure we won't need (de)compression we haven't got
+ */
+#ifndef HAVE_LIBZ
+ if (AH->compression != 0 && AH->PrintTocDataPtr !=NULL)
+ {
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if (te->hadDumper && (te->reqs & REQ_DATA) != 0)
+ exit_horribly(modulename, "cannot restore from compressed archive (compression not supported in this installation)\n");
+ }
+ }
+#endif
+
+ /*
+ * Prepare index arrays, so we can assume we have them throughout restore.
+ * It's possible we already did this, though.
+ */
+ if (AH->tocsByDumpId == NULL)
+ buildTocEntryArrays(AH);
+
+ /*
+ * If we're using a DB connection, then connect it.
+ */
+ if (ropt->useDB)
+ {
+ ahlog(AH, 1, "connecting to database for restore\n");
+ if (AH->version < K_VERS_1_3)
+ exit_horribly(modulename, "direct database connections are not supported in pre-1.3 archives\n");
+
+ /*
+ * We don't want to guess at whether the dump will successfully
+ * restore; allow the attempt regardless of the version of the restore
+ * target.
+ */
+ AHX->minRemoteVersion = 0;
+ AHX->maxRemoteVersion = 999999;
+
+ ConnectDatabase(AHX, ropt->dbname,
+ ropt->pghost, ropt->pgport, ropt->username,
+ ropt->promptPassword);
+
+ /*
+ * If we're talking to the DB directly, don't send comments since they
+ * obscure SQL when displaying errors
+ */
+ AH->noTocComments = 1;
+ }
+
+ /*
+ * Work out if we have an implied data-only restore. This can happen if
+ * the dump was data only or if the user has used a toc list to exclude
+ * all of the schema data. All we do is look for schema entries - if none
+ * are found then we set the dataOnly flag.
+ *
+ * We could scan for wanted TABLE entries, but that is not the same as
+ * dataOnly. At this stage, it seems unnecessary (6-Mar-2001).
+ */
+ if (!ropt->dataOnly)
+ {
+ int impliedDataOnly = 1;
+
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if ((te->reqs & REQ_SCHEMA) != 0)
+ { /* It's schema, and it's wanted */
+ impliedDataOnly = 0;
+ break;
+ }
+ }
+ if (impliedDataOnly)
+ {
+ ropt->dataOnly = impliedDataOnly;
+ ahlog(AH, 1, "implied data-only restore\n");
+ }
+ }
+
+ /*
+ * Setup the output file if necessary.
+ */
+ sav = SaveOutput(AH);
+ if (ropt->filename || ropt->compression)
+ SetOutput(AH, ropt->filename, ropt->compression);
+
+ ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
+
+ if (AH->public.verbose)
+ {
+ if (AH->archiveRemoteVersion)
+ ahprintf(AH, "-- Dumped from database version %s\n",
+ AH->archiveRemoteVersion);
+ if (AH->archiveDumpVersion)
+ ahprintf(AH, "-- Dumped by pg_dump version %s\n",
+ AH->archiveDumpVersion);
+ dumpTimestamp(AH, "Started on", AH->createDate);
+ }
+
+ if (ropt->single_txn)
+ {
+ if (AH->connection)
+ StartTransaction(AH);
+ else
+ ahprintf(AH, "BEGIN;\n\n");
+ }
+
+ /*
+ * Establish important parameter values right away.
+ */
+ _doSetFixedOutputState(AH);
+
+ AH->stage = STAGE_PROCESSING;
+
+ /*
+ * Drop the items at the start, in reverse order
+ */
+ if (ropt->dropSchema)
+ {
+ for (te = AH->toc->prev; te != AH->toc; te = te->prev)
+ {
+ AH->currentTE = te;
+
+ /*
+ * In createDB mode, issue a DROP *only* for the database as a
+ * whole. Issuing drops against anything else would be wrong,
+ * because at this point we're connected to the wrong database.
+ * Conversely, if we're not in createDB mode, we'd better not
+ * issue a DROP against the database at all.
+ */
+ if (ropt->createDB)
+ {
+ if (strcmp(te->desc, "DATABASE") != 0)
+ continue;
+ }
+ else
+ {
+ if (strcmp(te->desc, "DATABASE") == 0)
+ continue;
+ }
+
+ /* Otherwise, drop anything that's selected and has a dropStmt */
+ if (((te->reqs & (REQ_SCHEMA | REQ_DATA)) != 0) && te->dropStmt)
+ {
+ ahlog(AH, 1, "dropping %s %s\n", te->desc, te->tag);
+ /* Select owner and schema as necessary */
+ _becomeOwner(AH, te);
+ _selectOutputSchema(AH, te->namespace);
+
+ /*
+ * Now emit the DROP command, if the object has one. Note we
+ * don't necessarily emit it verbatim; at this point we add an
+ * appropriate IF EXISTS clause, if the user requested it.
+ */
+ if (*te->dropStmt != '\0')
+ {
+ if (!ropt->if_exists)
+ {
+ /* No --if-exists? Then just use the original */
+ ahprintf(AH, "%s", te->dropStmt);
+ }
+ else
+ {
+ /*
+ * Inject an appropriate spelling of "if exists". For
+ * large objects, we have a separate routine that
+ * knows how to do it, without depending on
+ * te->dropStmt; use that. For other objects we need
+ * to parse the command.
+ *
+ */
+ if (strncmp(te->desc, "BLOB", 4) == 0)
+ {
+ DropBlobIfExists(AH, te->catalogId.oid);
+ }
+ else
+ {
+ char buffer[40];
+ char *mark;
+ char *dropStmt = pg_strdup(te->dropStmt);
+ char *dropStmtPtr = dropStmt;
+ PQExpBuffer ftStmt = createPQExpBuffer();
+
+ /*
+ * Need to inject IF EXISTS clause after ALTER
+ * TABLE part in ALTER TABLE .. DROP statement
+ */
+ if (strncmp(dropStmt, "ALTER TABLE", 11) == 0)
+ {
+ appendPQExpBuffer(ftStmt,
+ "ALTER TABLE IF EXISTS");
+ dropStmt = dropStmt + 11;
+ }
+
+ /*
+ * ALTER TABLE..ALTER COLUMN..DROP DEFAULT does
+ * not support the IF EXISTS clause, and therefore
+ * we simply emit the original command for such
+ * objects. For other objects, we need to extract
+ * the first part of the DROP which includes the
+ * object type. Most of the time this matches
+ * te->desc, so search for that; however for the
+ * different kinds of CONSTRAINTs, we know to
+ * search for hardcoded "DROP CONSTRAINT" instead.
+ */
+ if (strcmp(te->desc, "DEFAULT") == 0)
+ appendPQExpBuffer(ftStmt, "%s", dropStmt);
+ else
+ {
+ if (strcmp(te->desc, "CONSTRAINT") == 0 ||
+ strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "FK CONSTRAINT") == 0)
+ strcpy(buffer, "DROP CONSTRAINT");
+ else
+ snprintf(buffer, sizeof(buffer), "DROP %s",
+ te->desc);
+
+ mark = strstr(dropStmt, buffer);
+ Assert(mark != NULL);
+
+ *mark = '\0';
+ appendPQExpBuffer(ftStmt, "%s%s IF EXISTS%s",
+ dropStmt, buffer,
+ mark + strlen(buffer));
+ }
+
+ ahprintf(AH, "%s", ftStmt->data);
+
+ destroyPQExpBuffer(ftStmt);
+
+ pg_free(dropStmtPtr);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * _selectOutputSchema may have set currSchema to reflect the effect
+ * of a "SET search_path" command it emitted. However, by now we may
+ * have dropped that schema; or it might not have existed in the first
+ * place. In either case the effective value of search_path will not
+ * be what we think. Forcibly reset currSchema so that we will
+ * re-establish the search_path setting when needed (after creating
+ * the schema).
+ *
+ * If we treated users as pg_dump'able objects then we'd need to reset
+ * currUser here too.
+ */
+ if (AH->currSchema)
+ free(AH->currSchema);
+ AH->currSchema = NULL;
+ }
+
+ /*
+ * In serial mode, we now process each non-ACL TOC entry.
+ *
+ * In parallel mode, turn control over to the parallel-restore logic.
+ */
+ if (parallel_mode)
+ {
+ ParallelState *pstate;
+ TocEntry pending_list;
+
+ par_list_header_init(&pending_list);
+
+ /* This runs PRE_DATA items and then disconnects from the database */
+ restore_toc_entries_prefork(AH);
+ Assert(AH->connection == NULL);
+
+ /* ParallelBackupStart() will actually fork the processes */
+ pstate = ParallelBackupStart(AH, ropt);
+ restore_toc_entries_parallel(AH, pstate, &pending_list);
+ ParallelBackupEnd(AH, pstate);
+
+ /* reconnect the master and see if we missed something */
+ restore_toc_entries_postfork(AH, &pending_list);
+ Assert(AH->connection != NULL);
+ }
+ else
+ {
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ (void) restore_toc_entry(AH, te, ropt, false);
+ }
+
+ /*
+ * Scan TOC again to output ownership commands and ACLs
+ */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ AH->currentTE = te;
+
+ /* Both schema and data objects might now have ownership/ACLs */
+ if ((te->reqs & (REQ_SCHEMA | REQ_DATA)) != 0)
+ {
+ ahlog(AH, 1, "setting owner and privileges for %s %s\n",
+ te->desc, te->tag);
+ _printTocEntry(AH, te, ropt, false, true);
+ }
+ }
+
+ if (ropt->single_txn)
+ {
+ if (AH->connection)
+ CommitTransaction(AH);
+ else
+ ahprintf(AH, "COMMIT;\n\n");
+ }
+
+ if (AH->public.verbose)
+ dumpTimestamp(AH, "Completed on", time(NULL));
+
+ ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n");
+
+ /*
+ * Clean up & we're done.
+ */
+ AH->stage = STAGE_FINALIZING;
+
+ if (ropt->filename || ropt->compression)
+ RestoreOutput(AH, sav);
+
+ if (ropt->useDB)
+ DisconnectDatabase(&AH->public);
+}
+
+/*
+ * Restore a single TOC item. Used in both parallel and non-parallel restore;
+ * is_parallel is true if we are in a worker child process.
+ *
+ * Returns 0 normally, but WORKER_CREATE_DONE or WORKER_INHIBIT_DATA if
+ * the parallel parent has to make the corresponding status update.
+ */
+static int
+restore_toc_entry(ArchiveHandle *AH, TocEntry *te,
+ RestoreOptions *ropt, bool is_parallel)
+{
+ int status = WORKER_OK;
+ teReqs reqs;
+ bool defnDumped;
+
+ AH->currentTE = te;
+
+ /* Work out what, if anything, we want from this entry */
+ if (_tocEntryIsACL(te))
+ reqs = 0; /* ACLs are never restored here */
+ else
+ reqs = te->reqs;
+
+ /*
+ * Ignore DATABASE entry unless we should create it. We must check this
+ * here, not in _tocEntryRequired, because the createDB option should not
+ * affect emitting a DATABASE entry to an archive file.
+ */
+ if (!ropt->createDB && strcmp(te->desc, "DATABASE") == 0)
+ reqs = 0;
+
+ /* Dump any relevant dump warnings to stderr */
+ if (!ropt->suppressDumpWarnings && strcmp(te->desc, "WARNING") == 0)
+ {
+ if (!ropt->dataOnly && te->defn != NULL && strlen(te->defn) != 0)
+ write_msg(modulename, "warning from original dump file: %s\n", te->defn);
+ else if (te->copyStmt != NULL && strlen(te->copyStmt) != 0)
+ write_msg(modulename, "warning from original dump file: %s\n", te->copyStmt);
+ }
+
+ defnDumped = false;
+
+ if ((reqs & REQ_SCHEMA) != 0) /* We want the schema */
+ {
+ ahlog(AH, 1, "creating %s %s\n", te->desc, te->tag);
+
+ _printTocEntry(AH, te, ropt, false, false);
+ defnDumped = true;
+
+ if (strcmp(te->desc, "TABLE") == 0)
+ {
+ if (AH->lastErrorTE == te)
+ {
+ /*
+ * We failed to create the table. If
+ * --no-data-for-failed-tables was given, mark the
+ * corresponding TABLE DATA to be ignored.
+ *
+ * In the parallel case this must be done in the parent, so we
+ * just set the return value.
+ */
+ if (ropt->noDataForFailedTables)
+ {
+ if (is_parallel)
+ status = WORKER_INHIBIT_DATA;
+ else
+ inhibit_data_for_failed_table(AH, te);
+ }
+ }
+ else
+ {
+ /*
+ * We created the table successfully. Mark the corresponding
+ * TABLE DATA for possible truncation.
+ *
+ * In the parallel case this must be done in the parent, so we
+ * just set the return value.
+ */
+ if (is_parallel)
+ status = WORKER_CREATE_DONE;
+ else
+ mark_create_done(AH, te);
+ }
+ }
+
+ /* If we created a DB, connect to it... */
+ if (strcmp(te->desc, "DATABASE") == 0)
+ {
+ ahlog(AH, 1, "connecting to new database \"%s\"\n", te->tag);
+ _reconnectToDB(AH, te->tag);
+ ropt->dbname = pg_strdup(te->tag);
+ }
+ }
+
+ /*
+ * If we have a data component, then process it
+ */
+ if ((reqs & REQ_DATA) != 0)
+ {
+ /*
+ * hadDumper will be set if there is genuine data component for this
+ * node. Otherwise, we need to check the defn field for statements
+ * that need to be executed in data-only restores.
+ */
+ if (te->hadDumper)
+ {
+ /*
+ * If we can output the data, then restore it.
+ */
+ if (AH->PrintTocDataPtr !=NULL)
+ {
+ _printTocEntry(AH, te, ropt, true, false);
+
+ if (strcmp(te->desc, "BLOBS") == 0 ||
+ strcmp(te->desc, "BLOB COMMENTS") == 0)
+ {
+ ahlog(AH, 1, "processing %s\n", te->desc);
+
+ _selectOutputSchema(AH, "pg_catalog");
+
+ /* Send BLOB COMMENTS data to ExecuteSimpleCommands() */
+ if (strcmp(te->desc, "BLOB COMMENTS") == 0)
+ AH->outputKind = OUTPUT_OTHERDATA;
+
+ (*AH->PrintTocDataPtr) (AH, te, ropt);
+
+ AH->outputKind = OUTPUT_SQLCMDS;
+ }
+ else
+ {
+ _disableTriggersIfNecessary(AH, te, ropt);
+
+ /* Select owner and schema as necessary */
+ _becomeOwner(AH, te);
+ _selectOutputSchema(AH, te->namespace);
+
+ ahlog(AH, 1, "processing data for table \"%s\"\n",
+ te->tag);
+
+ /*
+ * In parallel restore, if we created the table earlier in
+ * the run then we wrap the COPY in a transaction and
+ * precede it with a TRUNCATE. If archiving is not on
+ * this prevents WAL-logging the COPY. This obtains a
+ * speedup similar to that from using single_txn mode in
+ * non-parallel restores.
+ */
+ if (is_parallel && te->created)
+ {
+ /*
+ * Parallel restore is always talking directly to a
+ * server, so no need to see if we should issue BEGIN.
+ */
+ StartTransaction(AH);
+
+ /*
+ * If the server version is >= 8.4, make sure we issue
+ * TRUNCATE with ONLY so that child tables are not
+ * wiped.
+ */
+ ahprintf(AH, "TRUNCATE TABLE %s%s;\n\n",
+ (PQserverVersion(AH->connection) >= 80400 ?
+ "ONLY " : ""),
+ fmtId(te->tag));
+ }
+
+ /*
+ * If we have a copy statement, use it.
+ */
+ if (te->copyStmt && strlen(te->copyStmt) > 0)
+ {
+ ahprintf(AH, "%s", te->copyStmt);
+ AH->outputKind = OUTPUT_COPYDATA;
+ }
+ else
+ AH->outputKind = OUTPUT_OTHERDATA;
+
+ (*AH->PrintTocDataPtr) (AH, te, ropt);
+
+ /*
+ * Terminate COPY if needed.
+ */
+ if (AH->outputKind == OUTPUT_COPYDATA &&
+ RestoringToDB(AH))
+ EndDBCopyMode(AH, te);
+ AH->outputKind = OUTPUT_SQLCMDS;
+
+ /* close out the transaction started above */
+ if (is_parallel && te->created)
+ CommitTransaction(AH);
+
+ _enableTriggersIfNecessary(AH, te, ropt);
+ }
+ }
+ }
+ else if (!defnDumped)
+ {
+ /* If we haven't already dumped the defn part, do so now */
+ ahlog(AH, 1, "executing %s %s\n", te->desc, te->tag);
+ _printTocEntry(AH, te, ropt, false, false);
+ }
+ }
+
+ if (AH->public.n_errors > 0 && status == WORKER_OK)
+ status = WORKER_IGNORED_ERRORS;
+
+ return status;
+}
+
+/*
+ * Allocate a new RestoreOptions block.
+ * This is mainly so we can initialize it, but also for future expansion,
+ */
+RestoreOptions *
+NewRestoreOptions(void)
+{
+ RestoreOptions *opts;
+
+ opts = (RestoreOptions *) pg_malloc0(sizeof(RestoreOptions));
+
+ /* set any fields that shouldn't default to zeroes */
+ opts->format = archUnknown;
+ opts->promptPassword = TRI_DEFAULT;
+ opts->dumpSections = DUMP_UNSECTIONED;
+
+ return opts;
+}
+
+static void
+_disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt)
+{
+ /* This hack is only needed in a data-only restore */
+ if (!ropt->dataOnly || !ropt->disable_triggers)
+ return;
+
+ ahlog(AH, 1, "disabling triggers for %s\n", te->tag);
+
+ /*
+ * Become superuser if possible, since they are the only ones who can
+ * disable constraint triggers. If -S was not given, assume the initial
+ * user identity is a superuser. (XXX would it be better to become the
+ * table owner?)
+ */
+ _becomeUser(AH, ropt->superuser);
+
+ /*
+ * Disable them.
+ */
+ _selectOutputSchema(AH, te->namespace);
+
+ ahprintf(AH, "ALTER TABLE %s DISABLE TRIGGER ALL;\n\n",
+ fmtId(te->tag));
+}
+
+static void
+_enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt)
+{
+ /* This hack is only needed in a data-only restore */
+ if (!ropt->dataOnly || !ropt->disable_triggers)
+ return;
+
+ ahlog(AH, 1, "enabling triggers for %s\n", te->tag);
+
+ /*
+ * Become superuser if possible, since they are the only ones who can
+ * disable constraint triggers. If -S was not given, assume the initial
+ * user identity is a superuser. (XXX would it be better to become the
+ * table owner?)
+ */
+ _becomeUser(AH, ropt->superuser);
+
+ /*
+ * Enable them.
+ */
+ _selectOutputSchema(AH, te->namespace);
+
+ ahprintf(AH, "ALTER TABLE %s ENABLE TRIGGER ALL;\n\n",
+ fmtId(te->tag));
+}
+
+/*
+ * This is a routine that is part of the dumper interface, hence the 'Archive*' parameter.
+ */
+
+/* Public */
+void
+WriteData(Archive *AHX, const void *data, size_t dLen)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+
+ if (!AH->currToc)
+ exit_horribly(modulename, "internal error -- WriteData cannot be called outside the context of a DataDumper routine\n");
+
+ (*AH->WriteDataPtr) (AH, data, dLen);
+
+ return;
+}
+
+/*
+ * Create a new TOC entry. The TOC was designed as a TOC, but is now the
+ * repository for all metadata. But the name has stuck.
+ */
+
+/* Public */
+void
+ArchiveEntry(Archive *AHX,
+ CatalogId catalogId, DumpId dumpId,
+ const char *tag,
+ const char *namespace,
+ const char *tablespace,
+ const char *owner, bool withOids,
+ const char *desc, teSection section,
+ const char *defn,
+ const char *dropStmt, const char *copyStmt,
+ const DumpId *deps, int nDeps,
+ DataDumperPtr dumpFn, void *dumpArg)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ TocEntry *newToc;
+
+ newToc = (TocEntry *) pg_malloc0(sizeof(TocEntry));
+
+ AH->tocCount++;
+ if (dumpId > AH->maxDumpId)
+ AH->maxDumpId = dumpId;
+
+ newToc->prev = AH->toc->prev;
+ newToc->next = AH->toc;
+ AH->toc->prev->next = newToc;
+ AH->toc->prev = newToc;
+
+ newToc->catalogId = catalogId;
+ newToc->dumpId = dumpId;
+ newToc->section = section;
+
+ newToc->tag = pg_strdup(tag);
+ newToc->namespace = namespace ? pg_strdup(namespace) : NULL;
+ newToc->tablespace = tablespace ? pg_strdup(tablespace) : NULL;
+ newToc->owner = pg_strdup(owner);
+ newToc->withOids = withOids;
+ newToc->desc = pg_strdup(desc);
+ newToc->defn = pg_strdup(defn);
+ newToc->dropStmt = pg_strdup(dropStmt);
+ newToc->copyStmt = copyStmt ? pg_strdup(copyStmt) : NULL;
+
+ if (nDeps > 0)
+ {
+ newToc->dependencies = (DumpId *) pg_malloc(nDeps * sizeof(DumpId));
+ memcpy(newToc->dependencies, deps, nDeps * sizeof(DumpId));
+ newToc->nDeps = nDeps;
+ }
+ else
+ {
+ newToc->dependencies = NULL;
+ newToc->nDeps = 0;
+ }
+
+ newToc->dataDumper = dumpFn;
+ newToc->dataDumperArg = dumpArg;
+ newToc->hadDumper = dumpFn ? true : false;
+
+ newToc->formatData = NULL;
+
+ if (AH->ArchiveEntryPtr !=NULL)
+ (*AH->ArchiveEntryPtr) (AH, newToc);
+}
+
+/* Public */
+void
+PrintTOCSummary(Archive *AHX, RestoreOptions *ropt)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ TocEntry *te;
+ teSection curSection;
+ OutputContext sav;
+ const char *fmtName;
+
+ sav = SaveOutput(AH);
+ if (ropt->filename)
+ SetOutput(AH, ropt->filename, 0 /* no compression */ );
+
+ ahprintf(AH, ";\n; Archive created at %s", ctime(&AH->createDate));
+ ahprintf(AH, "; dbname: %s\n; TOC Entries: %d\n; Compression: %d\n",
+ AH->archdbname, AH->tocCount, AH->compression);
+
+ switch (AH->format)
+ {
+ case archCustom:
+ fmtName = "CUSTOM";
+ break;
+ case archDirectory:
+ fmtName = "DIRECTORY";
+ break;
+ case archTar:
+ fmtName = "TAR";
+ break;
+ default:
+ fmtName = "UNKNOWN";
+ }
+
+ ahprintf(AH, "; Dump Version: %d.%d-%d\n", AH->vmaj, AH->vmin, AH->vrev);
+ ahprintf(AH, "; Format: %s\n", fmtName);
+ ahprintf(AH, "; Integer: %d bytes\n", (int) AH->intSize);
+ ahprintf(AH, "; Offset: %d bytes\n", (int) AH->offSize);
+ if (AH->archiveRemoteVersion)
+ ahprintf(AH, "; Dumped from database version: %s\n",
+ AH->archiveRemoteVersion);
+ if (AH->archiveDumpVersion)
+ ahprintf(AH, "; Dumped by pg_dump version: %s\n",
+ AH->archiveDumpVersion);
+
+ ahprintf(AH, ";\n;\n; Selected TOC Entries:\n;\n");
+
+ curSection = SECTION_PRE_DATA;
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if (te->section != SECTION_NONE)
+ curSection = te->section;
+ if (ropt->verbose ||
+ (_tocEntryRequired(te, curSection, ropt) & (REQ_SCHEMA | REQ_DATA)) != 0)
+ ahprintf(AH, "%d; %u %u %s %s %s %s\n", te->dumpId,
+ te->catalogId.tableoid, te->catalogId.oid,
+ te->desc, te->namespace ? te->namespace : "-",
+ te->tag, te->owner);
+ if (ropt->verbose && te->nDeps > 0)
+ {
+ int i;
+
+ ahprintf(AH, ";\tdepends on:");
+ for (i = 0; i < te->nDeps; i++)
+ ahprintf(AH, " %d", te->dependencies[i]);
+ ahprintf(AH, "\n");
+ }
+ }
+
+ if (ropt->filename)
+ RestoreOutput(AH, sav);
+}
+
+/***********
+ * BLOB Archival
+ ***********/
+
+/* Called by a dumper to signal start of a BLOB */
+int
+StartBlob(Archive *AHX, Oid oid)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+
+ if (!AH->StartBlobPtr)
+ exit_horribly(modulename, "large-object output not supported in chosen format\n");
+
+ (*AH->StartBlobPtr) (AH, AH->currToc, oid);
+
+ return 1;
+}
+
+/* Called by a dumper to signal end of a BLOB */
+int
+EndBlob(Archive *AHX, Oid oid)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+
+ if (AH->EndBlobPtr)
+ (*AH->EndBlobPtr) (AH, AH->currToc, oid);
+
+ return 1;
+}
+
+/**********
+ * BLOB Restoration
+ **********/
+
+/*
+ * Called by a format handler before any blobs are restored
+ */
+void
+StartRestoreBlobs(ArchiveHandle *AH)
+{
+ if (!AH->ropt->single_txn)
+ {
+ if (AH->connection)
+ StartTransaction(AH);
+ else
+ ahprintf(AH, "BEGIN;\n\n");
+ }
+
+ AH->blobCount = 0;
+}
+
+/*
+ * Called by a format handler after all blobs are restored
+ */
+void
+EndRestoreBlobs(ArchiveHandle *AH)
+{
+ if (!AH->ropt->single_txn)
+ {
+ if (AH->connection)
+ CommitTransaction(AH);
+ else
+ ahprintf(AH, "COMMIT;\n\n");
+ }
+
+ ahlog(AH, 1, ngettext("restored %d large object\n",
+ "restored %d large objects\n",
+ AH->blobCount),
+ AH->blobCount);
+}
+
+
+/*
+ * Called by a format handler to initiate restoration of a blob
+ */
+void
+StartRestoreBlob(ArchiveHandle *AH, Oid oid, bool drop)
+{
+ bool old_blob_style = (AH->version < K_VERS_1_12);
+ Oid loOid;
+
+ AH->blobCount++;
+
+ /* Initialize the LO Buffer */
+ AH->lo_buf_used = 0;
+
+ ahlog(AH, 1, "restoring large object with OID %u\n", oid);
+
+ /* With an old archive we must do drop and create logic here */
+ if (old_blob_style && drop)
+ DropBlobIfExists(AH, oid);
+
+ if (AH->connection)
+ {
+ if (old_blob_style)
+ {
+ loOid = lo_create(AH->connection, oid);
+ if (loOid == 0 || loOid != oid)
+ exit_horribly(modulename, "could not create large object %u: %s",
+ oid, PQerrorMessage(AH->connection));
+ }
+ AH->loFd = lo_open(AH->connection, oid, INV_WRITE);
+ if (AH->loFd == -1)
+ exit_horribly(modulename, "could not open large object %u: %s",
+ oid, PQerrorMessage(AH->connection));
+ }
+ else
+ {
+ if (old_blob_style)
+ ahprintf(AH, "SELECT pg_catalog.lo_open(pg_catalog.lo_create('%u'), %d);\n",
+ oid, INV_WRITE);
+ else
+ ahprintf(AH, "SELECT pg_catalog.lo_open('%u', %d);\n",
+ oid, INV_WRITE);
+ }
+
+ AH->writingBlob = 1;
+}
+
+void
+EndRestoreBlob(ArchiveHandle *AH, Oid oid)
+{
+ if (AH->lo_buf_used > 0)
+ {
+ /* Write remaining bytes from the LO buffer */
+ dump_lo_buf(AH);
+ }
+
+ AH->writingBlob = 0;
+
+ if (AH->connection)
+ {
+ lo_close(AH->connection, AH->loFd);
+ AH->loFd = -1;
+ }
+ else
+ {
+ ahprintf(AH, "SELECT pg_catalog.lo_close(0);\n\n");
+ }
+}
+
+/***********
+ * Sorting and Reordering
+ ***********/
+
+void
+SortTocFromFile(Archive *AHX, RestoreOptions *ropt)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ FILE *fh;
+ char buf[100];
+ bool incomplete_line;
+
+ /* Allocate space for the 'wanted' array, and init it */
+ ropt->idWanted = (bool *) pg_malloc(sizeof(bool) * AH->maxDumpId);
+ memset(ropt->idWanted, 0, sizeof(bool) * AH->maxDumpId);
+
+ /* Setup the file */
+ fh = fopen(ropt->tocFile, PG_BINARY_R);
+ if (!fh)
+ exit_horribly(modulename, "could not open TOC file \"%s\": %s\n",
+ ropt->tocFile, strerror(errno));
+
+ incomplete_line = false;
+ while (fgets(buf, sizeof(buf), fh) != NULL)
+ {
+ bool prev_incomplete_line = incomplete_line;
+ int buflen;
+ char *cmnt;
+ char *endptr;
+ DumpId id;
+ TocEntry *te;
+
+ /*
+ * Some lines in the file might be longer than sizeof(buf). This is
+ * no problem, since we only care about the leading numeric ID which
+ * can be at most a few characters; but we have to skip continuation
+ * bufferloads when processing a long line.
+ */
+ buflen = strlen(buf);
+ if (buflen > 0 && buf[buflen - 1] == '\n')
+ incomplete_line = false;
+ else
+ incomplete_line = true;
+ if (prev_incomplete_line)
+ continue;
+
+ /* Truncate line at comment, if any */
+ cmnt = strchr(buf, ';');
+ if (cmnt != NULL)
+ cmnt[0] = '\0';
+
+ /* Ignore if all blank */
+ if (strspn(buf, " \t\r\n") == strlen(buf))
+ continue;
+
+ /* Get an ID, check it's valid and not already seen */
+ id = strtol(buf, &endptr, 10);
+ if (endptr == buf || id <= 0 || id > AH->maxDumpId ||
+ ropt->idWanted[id - 1])
+ {
+ write_msg(modulename, "WARNING: line ignored: %s\n", buf);
+ continue;
+ }
+
+ /* Find TOC entry */
+ te = getTocEntryByDumpId(AH, id);
+ if (!te)
+ exit_horribly(modulename, "could not find entry for ID %d\n",
+ id);
+
+ /* Mark it wanted */
+ ropt->idWanted[id - 1] = true;
+
+ /*
+ * Move each item to the end of the list as it is selected, so that
+ * they are placed in the desired order. Any unwanted items will end
+ * up at the front of the list, which may seem unintuitive but it's
+ * what we need. In an ordinary serial restore that makes no
+ * difference, but in a parallel restore we need to mark unrestored
+ * items' dependencies as satisfied before we start examining
+ * restorable items. Otherwise they could have surprising
+ * side-effects on the order in which restorable items actually get
+ * restored.
+ */
+ _moveBefore(AH, AH->toc, te);
+ }
+
+ if (fclose(fh) != 0)
+ exit_horribly(modulename, "could not close TOC file: %s\n",
+ strerror(errno));
+}
+
+/**********************
+ * 'Convenience functions that look like standard IO functions
+ * for writing data when in dump mode.
+ **********************/
+
+/* Public */
+void
+archputs(const char *s, Archive *AH)
+{
+ WriteData(AH, s, strlen(s));
+ return;
+}
+
+/* Public */
+int
+archprintf(Archive *AH, const char *fmt,...)
+{
+ char *p;
+ size_t len = 128; /* initial assumption about buffer size */
+ size_t cnt;
+
+ for (;;)
+ {
+ va_list args;
+
+ /* Allocate work buffer. */
+ p = (char *) pg_malloc(len);
+
+ /* Try to format the data. */
+ va_start(args, fmt);
+ cnt = pvsnprintf(p, len, fmt, args);
+ va_end(args);
+
+ if (cnt < len)
+ break; /* success */
+
+ /* Release buffer and loop around to try again with larger len. */
+ free(p);
+ len = cnt;
+ }
+
+ WriteData(AH, p, cnt);
+ free(p);
+ return (int) cnt;
+}
+
+
+/*******************************
+ * Stuff below here should be 'private' to the archiver routines
+ *******************************/
+
+static void
+SetOutput(ArchiveHandle *AH, const char *filename, int compression)
+{
+ int fn;
+
+ if (filename)
+ fn = -1;
+ else if (AH->FH)
+ fn = fileno(AH->FH);
+ else if (AH->fSpec)
+ {
+ fn = -1;
+ filename = AH->fSpec;
+ }
+ else
+ fn = fileno(stdout);
+
+ /* If compression explicitly requested, use gzopen */
+#ifdef HAVE_LIBZ
+ if (compression != 0)
+ {
+ char fmode[10];
+
+ /* Don't use PG_BINARY_x since this is zlib */
+ sprintf(fmode, "wb%d", compression);
+ if (fn >= 0)
+ AH->OF = gzdopen(dup(fn), fmode);
+ else
+ AH->OF = gzopen(filename, fmode);
+ AH->gzOut = 1;
+ }
+ else
+#endif
+ { /* Use fopen */
+ if (AH->mode == archModeAppend)
+ {
+ if (fn >= 0)
+ AH->OF = fdopen(dup(fn), PG_BINARY_A);
+ else
+ AH->OF = fopen(filename, PG_BINARY_A);
+ }
+ else
+ {
+ if (fn >= 0)
+ AH->OF = fdopen(dup(fn), PG_BINARY_W);
+ else
+ AH->OF = fopen(filename, PG_BINARY_W);
+ }
+ AH->gzOut = 0;
+ }
+
+ if (!AH->OF)
+ {
+ if (filename)
+ exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ filename, strerror(errno));
+ else
+ exit_horribly(modulename, "could not open output file: %s\n",
+ strerror(errno));
+ }
+}
+
+static OutputContext
+SaveOutput(ArchiveHandle *AH)
+{
+ OutputContext sav;
+
+ sav.OF = AH->OF;
+ sav.gzOut = AH->gzOut;
+
+ return sav;
+}
+
+static void
+RestoreOutput(ArchiveHandle *AH, OutputContext savedContext)
+{
+ int res;
+
+ if (AH->gzOut)
+ res = GZCLOSE(AH->OF);
+ else
+ res = fclose(AH->OF);
+
+ if (res != 0)
+ exit_horribly(modulename, "could not close output file: %s\n",
+ strerror(errno));
+
+ AH->gzOut = savedContext.gzOut;
+ AH->OF = savedContext.OF;
+}
+
+
+
+/*
+ * Print formatted text to the output file (usually stdout).
+ */
+int
+ahprintf(ArchiveHandle *AH, const char *fmt,...)
+{
+ char *p;
+ size_t len = 128; /* initial assumption about buffer size */
+ size_t cnt;
+
+ for (;;)
+ {
+ va_list args;
+
+ /* Allocate work buffer. */
+ p = (char *) pg_malloc(len);
+
+ /* Try to format the data. */
+ va_start(args, fmt);
+ cnt = pvsnprintf(p, len, fmt, args);
+ va_end(args);
+
+ if (cnt < len)
+ break; /* success */
+
+ /* Release buffer and loop around to try again with larger len. */
+ free(p);
+ len = cnt;
+ }
+
+ ahwrite(p, 1, cnt, AH);
+ free(p);
+ return (int) cnt;
+}
+
+void
+ahlog(ArchiveHandle *AH, int level, const char *fmt,...)
+{
+ va_list ap;
+
+ if (AH->debugLevel < level && (!AH->public.verbose || level > 1))
+ return;
+
+ va_start(ap, fmt);
+ vwrite_msg(NULL, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Single place for logic which says 'We are restoring to a direct DB connection'.
+ */
+static int
+RestoringToDB(ArchiveHandle *AH)
+{
+ return (AH->ropt && AH->ropt->useDB && AH->connection);
+}
+
+/*
+ * Dump the current contents of the LO data buffer while writing a BLOB
+ */
+static void
+dump_lo_buf(ArchiveHandle *AH)
+{
+ if (AH->connection)
+ {
+ size_t res;
+
+ res = lo_write(AH->connection, AH->loFd, AH->lo_buf, AH->lo_buf_used);
+ ahlog(AH, 5, ngettext("wrote %lu byte of large object data (result = %lu)\n",
+ "wrote %lu bytes of large object data (result = %lu)\n",
+ AH->lo_buf_used),
+ (unsigned long) AH->lo_buf_used, (unsigned long) res);
+ if (res != AH->lo_buf_used)
+ exit_horribly(modulename,
+ "could not write to large object (result: %lu, expected: %lu)\n",
+ (unsigned long) res, (unsigned long) AH->lo_buf_used);
+ }
+ else
+ {
+ PQExpBuffer buf = createPQExpBuffer();
+
+ appendByteaLiteralAHX(buf,
+ (const unsigned char *) AH->lo_buf,
+ AH->lo_buf_used,
+ AH);
+
+ /* Hack: turn off writingBlob so ahwrite doesn't recurse to here */
+ AH->writingBlob = 0;
+ ahprintf(AH, "SELECT pg_catalog.lowrite(0, %s);\n", buf->data);
+ AH->writingBlob = 1;
+
+ destroyPQExpBuffer(buf);
+ }
+ AH->lo_buf_used = 0;
+}
+
+
+/*
+ * Write buffer to the output file (usually stdout). This is used for
+ * outputting 'restore' scripts etc. It is even possible for an archive
+ * format to create a custom output routine to 'fake' a restore if it
+ * wants to generate a script (see TAR output).
+ */
+void
+ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH)
+{
+ int bytes_written = 0;
+
+ if (AH->writingBlob)
+ {
+ size_t remaining = size * nmemb;
+
+ while (AH->lo_buf_used + remaining > AH->lo_buf_size)
+ {
+ size_t avail = AH->lo_buf_size - AH->lo_buf_used;
+
+ memcpy((char *) AH->lo_buf + AH->lo_buf_used, ptr, avail);
+ ptr = (const void *) ((const char *) ptr + avail);
+ remaining -= avail;
+ AH->lo_buf_used += avail;
+ dump_lo_buf(AH);
+ }
+
+ memcpy((char *) AH->lo_buf + AH->lo_buf_used, ptr, remaining);
+ AH->lo_buf_used += remaining;
+
+ bytes_written = size * nmemb;
+ }
+ else if (AH->gzOut)
+ bytes_written = GZWRITE(ptr, size, nmemb, AH->OF);
+ else if (AH->CustomOutPtr)
+ bytes_written = AH->CustomOutPtr (AH, ptr, size * nmemb);
+
+ else
+ {
+ /*
+ * If we're doing a restore, and it's direct to DB, and we're
+ * connected then send it to the DB.
+ */
+ if (RestoringToDB(AH))
+ bytes_written = ExecuteSqlCommandBuf(AH, (const char *) ptr, size * nmemb);
+ else
+ bytes_written = fwrite(ptr, size, nmemb, AH->OF) * size;
+ }
+
+ if (bytes_written != size * nmemb)
+ WRITE_ERROR_EXIT;
+
+ return;
+}
+
+/* on some error, we may decide to go on... */
+void
+warn_or_exit_horribly(ArchiveHandle *AH,
+ const char *modulename, const char *fmt,...)
+{
+ va_list ap;
+
+ switch (AH->stage)
+ {
+
+ case STAGE_NONE:
+ /* Do nothing special */
+ break;
+
+ case STAGE_INITIALIZING:
+ if (AH->stage != AH->lastErrorStage)
+ write_msg(modulename, "Error while INITIALIZING:\n");
+ break;
+
+ case STAGE_PROCESSING:
+ if (AH->stage != AH->lastErrorStage)
+ write_msg(modulename, "Error while PROCESSING TOC:\n");
+ break;
+
+ case STAGE_FINALIZING:
+ if (AH->stage != AH->lastErrorStage)
+ write_msg(modulename, "Error while FINALIZING:\n");
+ break;
+ }
+ if (AH->currentTE != NULL && AH->currentTE != AH->lastErrorTE)
+ {
+ write_msg(modulename, "Error from TOC entry %d; %u %u %s %s %s\n",
+ AH->currentTE->dumpId,
+ AH->currentTE->catalogId.tableoid, AH->currentTE->catalogId.oid,
+ AH->currentTE->desc, AH->currentTE->tag, AH->currentTE->owner);
+ }
+ AH->lastErrorStage = AH->stage;
+ AH->lastErrorTE = AH->currentTE;
+
+ va_start(ap, fmt);
+ vwrite_msg(modulename, fmt, ap);
+ va_end(ap);
+
+ if (AH->public.exit_on_error)
+ exit_nicely(1);
+ else
+ AH->public.n_errors++;
+}
+
+#ifdef NOT_USED
+
+static void
+_moveAfter(ArchiveHandle *AH, TocEntry *pos, TocEntry *te)
+{
+ /* Unlink te from list */
+ te->prev->next = te->next;
+ te->next->prev = te->prev;
+
+ /* and insert it after "pos" */
+ te->prev = pos;
+ te->next = pos->next;
+ pos->next->prev = te;
+ pos->next = te;
+}
+#endif
+
+static void
+_moveBefore(ArchiveHandle *AH, TocEntry *pos, TocEntry *te)
+{
+ /* Unlink te from list */
+ te->prev->next = te->next;
+ te->next->prev = te->prev;
+
+ /* and insert it before "pos" */
+ te->prev = pos->prev;
+ te->next = pos;
+ pos->prev->next = te;
+ pos->prev = te;
+}
+
+/*
+ * Build index arrays for the TOC list
+ *
+ * This should be invoked only after we have created or read in all the TOC
+ * items.
+ *
+ * The arrays are indexed by dump ID (so entry zero is unused). Note that the
+ * array entries run only up to maxDumpId. We might see dependency dump IDs
+ * beyond that (if the dump was partial); so always check the array bound
+ * before trying to touch an array entry.
+ */
+static void
+buildTocEntryArrays(ArchiveHandle *AH)
+{
+ DumpId maxDumpId = AH->maxDumpId;
+ TocEntry *te;
+
+ AH->tocsByDumpId = (TocEntry **) pg_malloc0((maxDumpId + 1) * sizeof(TocEntry *));
+ AH->tableDataId = (DumpId *) pg_malloc0((maxDumpId + 1) * sizeof(DumpId));
+
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ /* this check is purely paranoia, maxDumpId should be correct */
+ if (te->dumpId <= 0 || te->dumpId > maxDumpId)
+ exit_horribly(modulename, "bad dumpId\n");
+
+ /* tocsByDumpId indexes all TOCs by their dump ID */
+ AH->tocsByDumpId[te->dumpId] = te;
+
+ /*
+ * tableDataId provides the TABLE DATA item's dump ID for each TABLE
+ * TOC entry that has a DATA item. We compute this by reversing the
+ * TABLE DATA item's dependency, knowing that a TABLE DATA item has
+ * just one dependency and it is the TABLE item.
+ */
+ if (strcmp(te->desc, "TABLE DATA") == 0 && te->nDeps > 0)
+ {
+ DumpId tableId = te->dependencies[0];
+
+ /*
+ * The TABLE item might not have been in the archive, if this was
+ * a data-only dump; but its dump ID should be less than its data
+ * item's dump ID, so there should be a place for it in the array.
+ */
+ if (tableId <= 0 || tableId > maxDumpId)
+ exit_horribly(modulename, "bad table dumpId for TABLE DATA item\n");
+
+ AH->tableDataId[tableId] = te->dumpId;
+ }
+ }
+}
+
+TocEntry *
+getTocEntryByDumpId(ArchiveHandle *AH, DumpId id)
+{
+ /* build index arrays if we didn't already */
+ if (AH->tocsByDumpId == NULL)
+ buildTocEntryArrays(AH);
+
+ if (id > 0 && id <= AH->maxDumpId)
+ return AH->tocsByDumpId[id];
+
+ return NULL;
+}
+
+teReqs
+TocIDRequired(ArchiveHandle *AH, DumpId id)
+{
+ TocEntry *te = getTocEntryByDumpId(AH, id);
+
+ if (!te)
+ return 0;
+
+ return te->reqs;
+}
+
+size_t
+WriteOffset(ArchiveHandle *AH, pgoff_t o, int wasSet)
+{
+ int off;
+
+ /* Save the flag */
+ (*AH->WriteBytePtr) (AH, wasSet);
+
+ /* Write out pgoff_t smallest byte first, prevents endian mismatch */
+ for (off = 0; off < sizeof(pgoff_t); off++)
+ {
+ (*AH->WriteBytePtr) (AH, o & 0xFF);
+ o >>= 8;
+ }
+ return sizeof(pgoff_t) + 1;
+}
+
+int
+ReadOffset(ArchiveHandle *AH, pgoff_t * o)
+{
+ int i;
+ int off;
+ int offsetFlg;
+
+ /* Initialize to zero */
+ *o = 0;
+
+ /* Check for old version */
+ if (AH->version < K_VERS_1_7)
+ {
+ /* Prior versions wrote offsets using WriteInt */
+ i = ReadInt(AH);
+ /* -1 means not set */
+ if (i < 0)
+ return K_OFFSET_POS_NOT_SET;
+ else if (i == 0)
+ return K_OFFSET_NO_DATA;
+
+ /* Cast to pgoff_t because it was written as an int. */
+ *o = (pgoff_t) i;
+ return K_OFFSET_POS_SET;
+ }
+
+ /*
+ * Read the flag indicating the state of the data pointer. Check if valid
+ * and die if not.
+ *
+ * This used to be handled by a negative or zero pointer, now we use an
+ * extra byte specifically for the state.
+ */
+ offsetFlg = (*AH->ReadBytePtr) (AH) & 0xFF;
+
+ switch (offsetFlg)
+ {
+ case K_OFFSET_POS_NOT_SET:
+ case K_OFFSET_NO_DATA:
+ case K_OFFSET_POS_SET:
+
+ break;
+
+ default:
+ exit_horribly(modulename, "unexpected data offset flag %d\n", offsetFlg);
+ }
+
+ /*
+ * Read the bytes
+ */
+ for (off = 0; off < AH->offSize; off++)
+ {
+ if (off < sizeof(pgoff_t))
+ *o |= ((pgoff_t) ((*AH->ReadBytePtr) (AH))) << (off * 8);
+ else
+ {
+ if ((*AH->ReadBytePtr) (AH) != 0)
+ exit_horribly(modulename, "file offset in dump file is too large\n");
+ }
+ }
+
+ return offsetFlg;
+}
+
+size_t
+WriteInt(ArchiveHandle *AH, int i)
+{
+ int b;
+
+ /*
+ * This is a bit yucky, but I don't want to make the binary format very
+ * dependent on representation, and not knowing much about it, I write out
+ * a sign byte. If you change this, don't forget to change the file
+ * version #, and modify readInt to read the new format AS WELL AS the old
+ * formats.
+ */
+
+ /* SIGN byte */
+ if (i < 0)
+ {
+ (*AH->WriteBytePtr) (AH, 1);
+ i = -i;
+ }
+ else
+ (*AH->WriteBytePtr) (AH, 0);
+
+ for (b = 0; b < AH->intSize; b++)
+ {
+ (*AH->WriteBytePtr) (AH, i & 0xFF);
+ i >>= 8;
+ }
+
+ return AH->intSize + 1;
+}
+
+int
+ReadInt(ArchiveHandle *AH)
+{
+ int res = 0;
+ int bv,
+ b;
+ int sign = 0; /* Default positive */
+ int bitShift = 0;
+
+ if (AH->version > K_VERS_1_0)
+ /* Read a sign byte */
+ sign = (*AH->ReadBytePtr) (AH);
+
+ for (b = 0; b < AH->intSize; b++)
+ {
+ bv = (*AH->ReadBytePtr) (AH) & 0xFF;
+ if (bv != 0)
+ res = res + (bv << bitShift);
+ bitShift += 8;
+ }
+
+ if (sign)
+ res = -res;
+
+ return res;
+}
+
+size_t
+WriteStr(ArchiveHandle *AH, const char *c)
+{
+ size_t res;
+
+ if (c)
+ {
+ int len = strlen(c);
+
+ res = WriteInt(AH, len);
+ (*AH->WriteBufPtr) (AH, c, len);
+ res += len;
+ }
+ else
+ res = WriteInt(AH, -1);
+
+ return res;
+}
+
+char *
+ReadStr(ArchiveHandle *AH)
+{
+ char *buf;
+ int l;
+
+ l = ReadInt(AH);
+ if (l < 0)
+ buf = NULL;
+ else
+ {
+ buf = (char *) pg_malloc(l + 1);
+ (*AH->ReadBufPtr) (AH, (void *) buf, l);
+
+ buf[l] = '\0';
+ }
+
+ return buf;
+}
+
+static int
+_discoverArchiveFormat(ArchiveHandle *AH)
+{
+ FILE *fh;
+ char sig[6]; /* More than enough */
+ size_t cnt;
+ int wantClose = 0;
+
+#if 0
+ write_msg(modulename, "attempting to ascertain archive format\n");
+#endif
+
+ if (AH->lookahead)
+ free(AH->lookahead);
+
+ AH->lookaheadSize = 512;
+ AH->lookahead = pg_malloc0(512);
+ AH->lookaheadLen = 0;
+ AH->lookaheadPos = 0;
+
+ if (AH->fSpec)
+ {
+ struct stat st;
+
+ wantClose = 1;
+
+ /*
+ * Check if the specified archive is a directory. If so, check if
+ * there's a "toc.dat" (or "toc.dat.gz") file in it.
+ */
+ if (stat(AH->fSpec, &st) == 0 && S_ISDIR(st.st_mode))
+ {
+ char buf[MAXPGPATH];
+
+ if (snprintf(buf, MAXPGPATH, "%s/toc.dat", AH->fSpec) >= MAXPGPATH)
+ exit_horribly(modulename, "directory name too long: \"%s\"\n",
+ AH->fSpec);
+ if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
+ {
+ AH->format = archDirectory;
+ return AH->format;
+ }
+
+#ifdef HAVE_LIBZ
+ if (snprintf(buf, MAXPGPATH, "%s/toc.dat.gz", AH->fSpec) >= MAXPGPATH)
+ exit_horribly(modulename, "directory name too long: \"%s\"\n",
+ AH->fSpec);
+ if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
+ {
+ AH->format = archDirectory;
+ return AH->format;
+ }
+#endif
+ exit_horribly(modulename, "directory \"%s\" does not appear to be a valid archive (\"toc.dat\" does not exist)\n",
+ AH->fSpec);
+ fh = NULL; /* keep compiler quiet */
+ }
+ else
+ {
+ fh = fopen(AH->fSpec, PG_BINARY_R);
+ if (!fh)
+ exit_horribly(modulename, "could not open input file \"%s\": %s\n",
+ AH->fSpec, strerror(errno));
+ }
+ }
+ else
+ {
+ fh = stdin;
+ if (!fh)
+ exit_horribly(modulename, "could not open input file: %s\n",
+ strerror(errno));
+ }
+
+ if ((cnt = fread(sig, 1, 5, fh)) != 5)
+ {
+ if (ferror(fh))
+ exit_horribly(modulename, "could not read input file: %s\n", strerror(errno));
+ else
+ exit_horribly(modulename, "input file is too short (read %lu, expected 5)\n",
+ (unsigned long) cnt);
+ }
+
+ /* Save it, just in case we need it later */
+ strncpy(&AH->lookahead[0], sig, 5);
+ AH->lookaheadLen = 5;
+
+ if (strncmp(sig, "PGDMP", 5) == 0)
+ {
+ int byteread;
+
+ /*
+ * Finish reading (most of) a custom-format header.
+ *
+ * NB: this code must agree with ReadHead().
+ */
+ if ((byteread = fgetc(fh)) == EOF)
+ READ_ERROR_EXIT(fh);
+
+ AH->vmaj = byteread;
+
+ if ((byteread = fgetc(fh)) == EOF)
+ READ_ERROR_EXIT(fh);
+
+ AH->vmin = byteread;
+
+ /* Save these too... */
+ AH->lookahead[AH->lookaheadLen++] = AH->vmaj;
+ AH->lookahead[AH->lookaheadLen++] = AH->vmin;
+
+ /* Check header version; varies from V1.0 */
+ if (AH->vmaj > 1 || ((AH->vmaj == 1) && (AH->vmin > 0))) /* Version > 1.0 */
+ {
+ if ((byteread = fgetc(fh)) == EOF)
+ READ_ERROR_EXIT(fh);
+
+ AH->vrev = byteread;
+ AH->lookahead[AH->lookaheadLen++] = AH->vrev;
+ }
+ else
+ AH->vrev = 0;
+
+ /* Make a convenient integer <maj><min><rev>00 */
+ AH->version = ((AH->vmaj * 256 + AH->vmin) * 256 + AH->vrev) * 256 + 0;
+
+ if ((AH->intSize = fgetc(fh)) == EOF)
+ READ_ERROR_EXIT(fh);
+ AH->lookahead[AH->lookaheadLen++] = AH->intSize;
+
+ if (AH->version >= K_VERS_1_7)
+ {
+ if ((AH->offSize = fgetc(fh)) == EOF)
+ READ_ERROR_EXIT(fh);
+ AH->lookahead[AH->lookaheadLen++] = AH->offSize;
+ }
+ else
+ AH->offSize = AH->intSize;
+
+ if ((byteread = fgetc(fh)) == EOF)
+ READ_ERROR_EXIT(fh);
+
+ AH->format = byteread;
+ AH->lookahead[AH->lookaheadLen++] = AH->format;
+ }
+ else
+ {
+ /*
+ * *Maybe* we have a tar archive format file or a text dump ... So,
+ * read first 512 byte header...
+ */
+ cnt = fread(&AH->lookahead[AH->lookaheadLen], 1, 512 - AH->lookaheadLen, fh);
+ /* read failure is checked below */
+ AH->lookaheadLen += cnt;
+
+ if (AH->lookaheadLen >= strlen(TEXT_DUMPALL_HEADER) &&
+ (strncmp(AH->lookahead, TEXT_DUMP_HEADER, strlen(TEXT_DUMP_HEADER)) == 0 ||
+ strncmp(AH->lookahead, TEXT_DUMPALL_HEADER, strlen(TEXT_DUMPALL_HEADER)) == 0))
+ {
+ /*
+ * looks like it's probably a text format dump. so suggest they
+ * try psql
+ */
+ exit_horribly(modulename, "input file appears to be a text format dump. Please use psql.\n");
+ }
+
+ if (AH->lookaheadLen != 512)
+ {
+ if (feof(fh))
+ exit_horribly(modulename, "input file does not appear to be a valid archive (too short?)\n");
+ else
+ READ_ERROR_EXIT(fh);
+ }
+
+ if (!isValidTarHeader(AH->lookahead))
+ exit_horribly(modulename, "input file does not appear to be a valid archive\n");
+
+ AH->format = archTar;
+ }
+
+ /* If we can't seek, then mark the header as read */
+ if (fseeko(fh, 0, SEEK_SET) != 0)
+ {
+ /*
+ * NOTE: Formats that use the lookahead buffer can unset this in their
+ * Init routine.
+ */
+ AH->readHeader = 1;
+ }
+ else
+ AH->lookaheadLen = 0; /* Don't bother since we've reset the file */
+
+ /* Close the file */
+ if (wantClose)
+ if (fclose(fh) != 0)
+ exit_horribly(modulename, "could not close input file: %s\n",
+ strerror(errno));
+
+ return AH->format;
+}
+
+
+/*
+ * Allocate an archive handle
+ */
+static ArchiveHandle *
+_allocAH(const char *FileSpec, const ArchiveFormat fmt,
+ const int compression, ArchiveMode mode, SetupWorkerPtr setupWorkerPtr)
+{
+ ArchiveHandle *AH;
+
+#if 0
+ write_msg(modulename, "allocating AH for %s, format %d\n", FileSpec, fmt);
+#endif
+
+ AH = (ArchiveHandle *) pg_malloc0(sizeof(ArchiveHandle));
+
+ /* AH->debugLevel = 100; */
+
+ AH->vmaj = K_VERS_MAJOR;
+ AH->vmin = K_VERS_MINOR;
+ AH->vrev = K_VERS_REV;
+
+ /* Make a convenient integer <maj><min><rev>00 */
+ AH->version = ((AH->vmaj * 256 + AH->vmin) * 256 + AH->vrev) * 256 + 0;
+
+ /* initialize for backwards compatible string processing */
+ AH->public.encoding = 0; /* PG_SQL_ASCII */
+ AH->public.std_strings = false;
+
+ /* sql error handling */
+ AH->public.exit_on_error = true;
+ AH->public.n_errors = 0;
+
+ AH->archiveDumpVersion = PG_VERSION;
+
+ AH->createDate = time(NULL);
+
+ AH->intSize = sizeof(int);
+ AH->offSize = sizeof(pgoff_t);
+ if (FileSpec)
+ {
+ AH->fSpec = pg_strdup(FileSpec);
+
+ /*
+ * Not used; maybe later....
+ *
+ * AH->workDir = pg_strdup(FileSpec); for(i=strlen(FileSpec) ; i > 0 ;
+ * i--) if (AH->workDir[i-1] == '/')
+ */
+ }
+ else
+ AH->fSpec = NULL;
+
+ AH->currUser = NULL; /* unknown */
+ AH->currSchema = NULL; /* ditto */
+ AH->currTablespace = NULL; /* ditto */
+ AH->currWithOids = -1; /* force SET */
+
+ AH->toc = (TocEntry *) pg_malloc0(sizeof(TocEntry));
+
+ AH->toc->next = AH->toc;
+ AH->toc->prev = AH->toc;
+
+ AH->mode = mode;
+ AH->compression = compression;
+
+ memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
+
+ /* Open stdout with no compression for AH output handle */
+ AH->gzOut = 0;
+ AH->OF = stdout;
+
+ /*
+ * On Windows, we need to use binary mode to read/write non-text archive
+ * formats. Force stdin/stdout into binary mode if that is what we are
+ * using.
+ */
+#ifdef WIN32
+ if (fmt != archNull &&
+ (AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0))
+ {
+ if (mode == archModeWrite)
+ setmode(fileno(stdout), O_BINARY);
+ else
+ setmode(fileno(stdin), O_BINARY);
+ }
+#endif
+
+ AH->SetupWorkerPtr = setupWorkerPtr;
+
+ if (fmt == archUnknown)
+ AH->format = _discoverArchiveFormat(AH);
+ else
+ AH->format = fmt;
+
+ AH->promptPassword = TRI_DEFAULT;
+
+ switch (AH->format)
+ {
+ case archCustom:
+ InitArchiveFmt_Custom(AH);
+ break;
+
+ case archNull:
+ InitArchiveFmt_Null(AH);
+ break;
+
+ case archDirectory:
+ InitArchiveFmt_Directory(AH);
+ break;
+
+ case archTar:
+ InitArchiveFmt_Tar(AH);
+ break;
+
+ default:
+ exit_horribly(modulename, "unrecognized file format \"%d\"\n", fmt);
+ }
+
+ return AH;
+}
+
+void
+WriteDataChunks(ArchiveHandle *AH, ParallelState *pstate)
+{
+ TocEntry *te;
+
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if (!te->dataDumper)
+ continue;
+
+ if ((te->reqs & REQ_DATA) == 0)
+ continue;
+
+ if (pstate && pstate->numWorkers > 1)
+ {
+ /*
+ * If we are in a parallel backup, then we are always the master
+ * process.
+ */
+ EnsureIdleWorker(AH, pstate);
+ Assert(GetIdleWorker(pstate) != NO_SLOT);
+ DispatchJobForTocEntry(AH, pstate, te, ACT_DUMP);
+ }
+ else
+ WriteDataChunksForTocEntry(AH, te);
+ }
+ EnsureWorkersFinished(AH, pstate);
+}
+
+void
+WriteDataChunksForTocEntry(ArchiveHandle *AH, TocEntry *te)
+{
+ StartDataPtr startPtr;
+ EndDataPtr endPtr;
+
+ AH->currToc = te;
+
+ if (strcmp(te->desc, "BLOBS") == 0)
+ {
+ startPtr = AH->StartBlobsPtr;
+ endPtr = AH->EndBlobsPtr;
+ }
+ else
+ {
+ startPtr = AH->StartDataPtr;
+ endPtr = AH->EndDataPtr;
+ }
+
+ if (startPtr != NULL)
+ (*startPtr) (AH, te);
+
+ /*
+ * The user-provided DataDumper routine needs to call AH->WriteData
+ */
+ (*te->dataDumper) ((Archive *) AH, te->dataDumperArg);
+
+ if (endPtr != NULL)
+ (*endPtr) (AH, te);
+
+ AH->currToc = NULL;
+}
+
+void
+WriteToc(ArchiveHandle *AH)
+{
+ TocEntry *te;
+ char workbuf[32];
+ int tocCount;
+ int i;
+
+ /* count entries that will actually be dumped */
+ tocCount = 0;
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_SPECIAL)) != 0)
+ tocCount++;
+ }
+
+ /* printf("%d TOC Entries to save\n", tocCount); */
+
+ WriteInt(AH, tocCount);
+
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_SPECIAL)) == 0)
+ continue;
+
+ WriteInt(AH, te->dumpId);
+ WriteInt(AH, te->dataDumper ? 1 : 0);
+
+ /* OID is recorded as a string for historical reasons */
+ sprintf(workbuf, "%u", te->catalogId.tableoid);
+ WriteStr(AH, workbuf);
+ sprintf(workbuf, "%u", te->catalogId.oid);
+ WriteStr(AH, workbuf);
+
+ WriteStr(AH, te->tag);
+ WriteStr(AH, te->desc);
+ WriteInt(AH, te->section);
+ WriteStr(AH, te->defn);
+ WriteStr(AH, te->dropStmt);
+ WriteStr(AH, te->copyStmt);
+ WriteStr(AH, te->namespace);
+ WriteStr(AH, te->tablespace);
+ WriteStr(AH, te->owner);
+ WriteStr(AH, te->withOids ? "true" : "false");
+
+ /* Dump list of dependencies */
+ for (i = 0; i < te->nDeps; i++)
+ {
+ sprintf(workbuf, "%d", te->dependencies[i]);
+ WriteStr(AH, workbuf);
+ }
+ WriteStr(AH, NULL); /* Terminate List */
+
+ if (AH->WriteExtraTocPtr)
+ (*AH->WriteExtraTocPtr) (AH, te);
+ }
+}
+
+void
+ReadToc(ArchiveHandle *AH)
+{
+ int i;
+ char *tmp;
+ DumpId *deps;
+ int depIdx;
+ int depSize;
+ TocEntry *te;
+
+ AH->tocCount = ReadInt(AH);
+ AH->maxDumpId = 0;
+
+ for (i = 0; i < AH->tocCount; i++)
+ {
+ te = (TocEntry *) pg_malloc0(sizeof(TocEntry));
+ te->dumpId = ReadInt(AH);
+
+ if (te->dumpId > AH->maxDumpId)
+ AH->maxDumpId = te->dumpId;
+
+ /* Sanity check */
+ if (te->dumpId <= 0)
+ exit_horribly(modulename,
+ "entry ID %d out of range -- perhaps a corrupt TOC\n",
+ te->dumpId);
+
+ te->hadDumper = ReadInt(AH);
+
+ if (AH->version >= K_VERS_1_8)
+ {
+ tmp = ReadStr(AH);
+ sscanf(tmp, "%u", &te->catalogId.tableoid);
+ free(tmp);
+ }
+ else
+ te->catalogId.tableoid = InvalidOid;
+ tmp = ReadStr(AH);
+ sscanf(tmp, "%u", &te->catalogId.oid);
+ free(tmp);
+
+ te->tag = ReadStr(AH);
+ te->desc = ReadStr(AH);
+
+ if (AH->version >= K_VERS_1_11)
+ {
+ te->section = ReadInt(AH);
+ }
+ else
+ {
+ /*
+ * Rules for pre-8.4 archives wherein pg_dump hasn't classified
+ * the entries into sections. This list need not cover entry
+ * types added later than 8.4.
+ */
+ if (strcmp(te->desc, "COMMENT") == 0 ||
+ strcmp(te->desc, "ACL") == 0 ||
+ strcmp(te->desc, "ACL LANGUAGE") == 0)
+ te->section = SECTION_NONE;
+ else if (strcmp(te->desc, "TABLE DATA") == 0 ||
+ strcmp(te->desc, "BLOBS") == 0 ||
+ strcmp(te->desc, "BLOB COMMENTS") == 0)
+ te->section = SECTION_DATA;
+ else if (strcmp(te->desc, "CONSTRAINT") == 0 ||
+ strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "INDEX") == 0 ||
+ strcmp(te->desc, "RULE") == 0 ||
+ strcmp(te->desc, "TRIGGER") == 0)
+ te->section = SECTION_POST_DATA;
+ else
+ te->section = SECTION_PRE_DATA;
+ }
+
+ te->defn = ReadStr(AH);
+ te->dropStmt = ReadStr(AH);
+
+ if (AH->version >= K_VERS_1_3)
+ te->copyStmt = ReadStr(AH);
+
+ if (AH->version >= K_VERS_1_6)
+ te->namespace = ReadStr(AH);
+
+ if (AH->version >= K_VERS_1_10)
+ te->tablespace = ReadStr(AH);
+
+ te->owner = ReadStr(AH);
+ if (AH->version >= K_VERS_1_9)
+ {
+ if (strcmp(ReadStr(AH), "true") == 0)
+ te->withOids = true;
+ else
+ te->withOids = false;
+ }
+ else
+ te->withOids = true;
+
+ /* Read TOC entry dependencies */
+ if (AH->version >= K_VERS_1_5)
+ {
+ depSize = 100;
+ deps = (DumpId *) pg_malloc(sizeof(DumpId) * depSize);
+ depIdx = 0;
+ for (;;)
+ {
+ tmp = ReadStr(AH);
+ if (!tmp)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ sscanf(tmp, "%d", &deps[depIdx]);
+ free(tmp);
+ depIdx++;
+ }
+
+ if (depIdx > 0) /* We have a non-null entry */
+ {
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depIdx);
+ te->dependencies = deps;
+ te->nDeps = depIdx;
+ }
+ else
+ {
+ free(deps);
+ te->dependencies = NULL;
+ te->nDeps = 0;
+ }
+ }
+ else
+ {
+ te->dependencies = NULL;
+ te->nDeps = 0;
+ }
+
+ if (AH->ReadExtraTocPtr)
+ (*AH->ReadExtraTocPtr) (AH, te);
+
+ ahlog(AH, 3, "read TOC entry %d (ID %d) for %s %s\n",
+ i, te->dumpId, te->desc, te->tag);
+
+ /* link completed entry into TOC circular list */
+ te->prev = AH->toc->prev;
+ AH->toc->prev->next = te;
+ AH->toc->prev = te;
+ te->next = AH->toc;
+
+ /* special processing immediately upon read for some items */
+ if (strcmp(te->desc, "ENCODING") == 0)
+ processEncodingEntry(AH, te);
+ else if (strcmp(te->desc, "STDSTRINGS") == 0)
+ processStdStringsEntry(AH, te);
+ }
+}
+
+static void
+processEncodingEntry(ArchiveHandle *AH, TocEntry *te)
+{
+ /* te->defn should have the form SET client_encoding = 'foo'; */
+ char *defn = pg_strdup(te->defn);
+ char *ptr1;
+ char *ptr2 = NULL;
+ int encoding;
+
+ ptr1 = strchr(defn, '\'');
+ if (ptr1)
+ ptr2 = strchr(++ptr1, '\'');
+ if (ptr2)
+ {
+ *ptr2 = '\0';
+ encoding = pg_char_to_encoding(ptr1);
+ if (encoding < 0)
+ exit_horribly(modulename, "unrecognized encoding \"%s\"\n",
+ ptr1);
+ AH->public.encoding = encoding;
+ }
+ else
+ exit_horribly(modulename, "invalid ENCODING item: %s\n",
+ te->defn);
+
+ free(defn);
+}
+
+static void
+processStdStringsEntry(ArchiveHandle *AH, TocEntry *te)
+{
+ /* te->defn should have the form SET standard_conforming_strings = 'x'; */
+ char *ptr1;
+
+ ptr1 = strchr(te->defn, '\'');
+ if (ptr1 && strncmp(ptr1, "'on'", 4) == 0)
+ AH->public.std_strings = true;
+ else if (ptr1 && strncmp(ptr1, "'off'", 5) == 0)
+ AH->public.std_strings = false;
+ else
+ exit_horribly(modulename, "invalid STDSTRINGS item: %s\n",
+ te->defn);
+}
+
+static teReqs
+_tocEntryRequired(TocEntry *te, teSection curSection, RestoreOptions *ropt)
+{
+ teReqs res = REQ_SCHEMA | REQ_DATA;
+
+ /* ENCODING and STDSTRINGS items are treated specially */
+ if (strcmp(te->desc, "ENCODING") == 0 ||
+ strcmp(te->desc, "STDSTRINGS") == 0)
+ return REQ_SPECIAL;
+
+ /* If it's an ACL, maybe ignore it */
+ if (ropt->aclsSkip && _tocEntryIsACL(te))
+ return 0;
+
+ /* If it's security labels, maybe ignore it */
+ if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0)
+ return 0;
+
+ /* Ignore it if section is not to be dumped/restored */
+ switch (curSection)
+ {
+ case SECTION_PRE_DATA:
+ if (!(ropt->dumpSections & DUMP_PRE_DATA))
+ return 0;
+ break;
+ case SECTION_DATA:
+ if (!(ropt->dumpSections & DUMP_DATA))
+ return 0;
+ break;
+ case SECTION_POST_DATA:
+ if (!(ropt->dumpSections & DUMP_POST_DATA))
+ return 0;
+ break;
+ default:
+ /* shouldn't get here, really, but ignore it */
+ return 0;
+ }
+
+ /* Check options for selective dump/restore */
+ if (ropt->schemaNames.head != NULL)
+ {
+ /* If no namespace is specified, it means all. */
+ if (!te->namespace)
+ return 0;
+ if (!(simple_string_list_member(&ropt->schemaNames, te->namespace)))
+ return 0;
+ }
+
+ if (ropt->selTypes)
+ {
+ if (strcmp(te->desc, "TABLE") == 0 ||
+ strcmp(te->desc, "TABLE DATA") == 0)
+ {
+ if (!ropt->selTable)
+ return 0;
+ if (ropt->tableNames.head != NULL && (!(simple_string_list_member(&ropt->tableNames, te->tag))))
+ return 0;
+ }
+ else if (strcmp(te->desc, "INDEX") == 0)
+ {
+ if (!ropt->selIndex)
+ return 0;
+ if (ropt->indexNames.head != NULL && (!(simple_string_list_member(&ropt->indexNames, te->tag))))
+ return 0;
+ }
+ else if (strcmp(te->desc, "FUNCTION") == 0)
+ {
+ if (!ropt->selFunction)
+ return 0;
+ if (ropt->functionNames.head != NULL && (!(simple_string_list_member(&ropt->functionNames, te->tag))))
+ return 0;
+ }
+ else if (strcmp(te->desc, "TRIGGER") == 0)
+ {
+ if (!ropt->selTrigger)
+ return 0;
+ if (ropt->triggerNames.head != NULL && (!(simple_string_list_member(&ropt->triggerNames, te->tag))))
+ return 0;
+ }
+ else
+ return 0;
+ }
+
+ /*
+ * Check if we had a dataDumper. Indicates if the entry is schema or data
+ */
+ if (!te->hadDumper)
+ {
+ /*
+ * Special Case: If 'SEQUENCE SET' or anything to do with BLOBs, then
+ * it is considered a data entry. We don't need to check for the
+ * BLOBS entry or old-style BLOB COMMENTS, because they will have
+ * hadDumper = true ... but we do need to check new-style BLOB
+ * comments.
+ */
+ if (strcmp(te->desc, "SEQUENCE SET") == 0 ||
+ strcmp(te->desc, "BLOB") == 0 ||
+ (strcmp(te->desc, "ACL") == 0 &&
+ strncmp(te->tag, "LARGE OBJECT ", 13) == 0) ||
+ (strcmp(te->desc, "COMMENT") == 0 &&
+ strncmp(te->tag, "LARGE OBJECT ", 13) == 0) ||
+ (strcmp(te->desc, "SECURITY LABEL") == 0 &&
+ strncmp(te->tag, "LARGE OBJECT ", 13) == 0))
+ res = res & REQ_DATA;
+ else
+ res = res & ~REQ_DATA;
+ }
+
+ /*
+ * Special case: <Init> type with <Max OID> tag; this is obsolete and we
+ * always ignore it.
+ */
+ if ((strcmp(te->desc, "<Init>") == 0) && (strcmp(te->tag, "Max OID") == 0))
+ return 0;
+
+ /* Mask it if we only want schema */
+ if (ropt->schemaOnly)
+ res = res & REQ_SCHEMA;
+
+ /* Mask it if we only want data */
+ if (ropt->dataOnly)
+ res = res & REQ_DATA;
+
+ /* Mask it if we don't have a schema contribution */
+ if (!te->defn || strlen(te->defn) == 0)
+ res = res & ~REQ_SCHEMA;
+
+ /* Finally, if there's a per-ID filter, limit based on that as well */
+ if (ropt->idWanted && !ropt->idWanted[te->dumpId - 1])
+ return 0;
+
+ return res;
+}
+
+/*
+ * Identify TOC entries that are ACLs.
+ */
+static bool
+_tocEntryIsACL(TocEntry *te)
+{
+ /* "ACL LANGUAGE" was a crock emitted only in PG 7.4 */
+ if (strcmp(te->desc, "ACL") == 0 ||
+ strcmp(te->desc, "ACL LANGUAGE") == 0 ||
+ strcmp(te->desc, "DEFAULT ACL") == 0)
+ return true;
+ return false;
+}
+
+/*
+ * Issue SET commands for parameters that we want to have set the same way
+ * at all times during execution of a restore script.
+ */
+static void
+_doSetFixedOutputState(ArchiveHandle *AH)
+{
+ /* Disable statement_timeout since restore is probably slow */
+ ahprintf(AH, "SET statement_timeout = 0;\n");
+
+ /* Likewise for lock_timeout */
+ ahprintf(AH, "SET lock_timeout = 0;\n");
+
+ /* Select the correct character set encoding */
+ ahprintf(AH, "SET client_encoding = '%s';\n",
+ pg_encoding_to_char(AH->public.encoding));
+
+ /* Select the correct string literal syntax */
+ ahprintf(AH, "SET standard_conforming_strings = %s;\n",
+ AH->public.std_strings ? "on" : "off");
+
+ /* Select the role to be used during restore */
+ if (AH->ropt && AH->ropt->use_role)
+ ahprintf(AH, "SET ROLE %s;\n", fmtId(AH->ropt->use_role));
+
+ /* Make sure function checking is disabled */
+ ahprintf(AH, "SET check_function_bodies = false;\n");
+
+ /* Avoid annoying notices etc */
+ ahprintf(AH, "SET client_min_messages = warning;\n");
+ if (!AH->public.std_strings)
+ ahprintf(AH, "SET escape_string_warning = off;\n");
+
+ ahprintf(AH, "\n");
+}
+
+/*
+ * Issue a SET SESSION AUTHORIZATION command. Caller is responsible
+ * for updating state if appropriate. If user is NULL or an empty string,
+ * the specification DEFAULT will be used.
+ */
+static void
+_doSetSessionAuth(ArchiveHandle *AH, const char *user)
+{
+ PQExpBuffer cmd = createPQExpBuffer();
+
+ appendPQExpBufferStr(cmd, "SET SESSION AUTHORIZATION ");
+
+ /*
+ * SQL requires a string literal here. Might as well be correct.
+ */
+ if (user && *user)
+ appendStringLiteralAHX(cmd, user, AH);
+ else
+ appendPQExpBufferStr(cmd, "DEFAULT");
+ appendPQExpBufferChar(cmd, ';');
+
+ if (RestoringToDB(AH))
+ {
+ PGresult *res;
+
+ res = PQexec(AH->connection, cmd->data);
+
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+ /* NOT warn_or_exit_horribly... use -O instead to skip this. */
+ exit_horribly(modulename, "could not set session user to \"%s\": %s",
+ user, PQerrorMessage(AH->connection));
+
+ PQclear(res);
+ }
+ else
+ ahprintf(AH, "%s\n\n", cmd->data);
+
+ destroyPQExpBuffer(cmd);
+}
+
+
+/*
+ * Issue a SET default_with_oids command. Caller is responsible
+ * for updating state if appropriate.
+ */
+static void
+_doSetWithOids(ArchiveHandle *AH, const bool withOids)
+{
+ PQExpBuffer cmd = createPQExpBuffer();
+
+ appendPQExpBuffer(cmd, "SET default_with_oids = %s;", withOids ?
+ "true" : "false");
+
+ if (RestoringToDB(AH))
+ {
+ PGresult *res;
+
+ res = PQexec(AH->connection, cmd->data);
+
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+ warn_or_exit_horribly(AH, modulename,
+ "could not set default_with_oids: %s",
+ PQerrorMessage(AH->connection));
+
+ PQclear(res);
+ }
+ else
+ ahprintf(AH, "%s\n\n", cmd->data);
+
+ destroyPQExpBuffer(cmd);
+}
+
+
+/*
+ * Issue the commands to connect to the specified database.
+ *
+ * If we're currently restoring right into a database, this will
+ * actually establish a connection. Otherwise it puts a \connect into
+ * the script output.
+ *
+ * NULL dbname implies reconnecting to the current DB (pretty useless).
+ */
+static void
+_reconnectToDB(ArchiveHandle *AH, const char *dbname)
+{
+ if (RestoringToDB(AH))
+ ReconnectToServer(AH, dbname, NULL);
+ else
+ {
+ PQExpBuffer qry = createPQExpBuffer();
+
+ appendPQExpBuffer(qry, "\\connect %s\n\n",
+ dbname ? fmtId(dbname) : "-");
+ ahprintf(AH, "%s", qry->data);
+ destroyPQExpBuffer(qry);
+ }
+
+ /*
+ * NOTE: currUser keeps track of what the imaginary session user in our
+ * script is. It's now effectively reset to the original userID.
+ */
+ if (AH->currUser)
+ free(AH->currUser);
+ AH->currUser = NULL;
+
+ /* don't assume we still know the output schema, tablespace, etc either */
+ if (AH->currSchema)
+ free(AH->currSchema);
+ AH->currSchema = NULL;
+ if (AH->currTablespace)
+ free(AH->currTablespace);
+ AH->currTablespace = NULL;
+ AH->currWithOids = -1;
+
+ /* re-establish fixed state */
+ _doSetFixedOutputState(AH);
+}
+
+/*
+ * Become the specified user, and update state to avoid redundant commands
+ *
+ * NULL or empty argument is taken to mean restoring the session default
+ */
+static void
+_becomeUser(ArchiveHandle *AH, const char *user)
+{
+ if (!user)
+ user = ""; /* avoid null pointers */
+
+ if (AH->currUser && strcmp(AH->currUser, user) == 0)
+ return; /* no need to do anything */
+
+ _doSetSessionAuth(AH, user);
+
+ /*
+ * NOTE: currUser keeps track of what the imaginary session user in our
+ * script is
+ */
+ if (AH->currUser)
+ free(AH->currUser);
+ AH->currUser = pg_strdup(user);
+}
+
+/*
+ * Become the owner of the given TOC entry object. If
+ * changes in ownership are not allowed, this doesn't do anything.
+ */
+static void
+_becomeOwner(ArchiveHandle *AH, TocEntry *te)
+{
+ if (AH->ropt && (AH->ropt->noOwner || !AH->ropt->use_setsessauth))
+ return;
+
+ _becomeUser(AH, te->owner);
+}
+
+
+/*
+ * Set the proper default_with_oids value for the table.
+ */
+static void
+_setWithOids(ArchiveHandle *AH, TocEntry *te)
+{
+ if (AH->currWithOids != te->withOids)
+ {
+ _doSetWithOids(AH, te->withOids);
+ AH->currWithOids = te->withOids;
+ }
+}
+
+
+/*
+ * Issue the commands to select the specified schema as the current schema
+ * in the target database.
+ */
+static void
+_selectOutputSchema(ArchiveHandle *AH, const char *schemaName)
+{
+ PQExpBuffer qry;
+
+ if (!schemaName || *schemaName == '\0' ||
+ (AH->currSchema && strcmp(AH->currSchema, schemaName) == 0))
+ return; /* no need to do anything */
+
+ qry = createPQExpBuffer();
+
+ appendPQExpBuffer(qry, "SET search_path = %s",
+ fmtId(schemaName));
+ if (strcmp(schemaName, "pg_catalog") != 0)
+ appendPQExpBufferStr(qry, ", pg_catalog");
+
+ if (RestoringToDB(AH))
+ {
+ PGresult *res;
+
+ res = PQexec(AH->connection, qry->data);
+
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+ warn_or_exit_horribly(AH, modulename,
+ "could not set search_path to \"%s\": %s",
+ schemaName, PQerrorMessage(AH->connection));
+
+ PQclear(res);
+ }
+ else
+ ahprintf(AH, "%s;\n\n", qry->data);
+
+ if (AH->currSchema)
+ free(AH->currSchema);
+ AH->currSchema = pg_strdup(schemaName);
+
+ destroyPQExpBuffer(qry);
+}
+
+/*
+ * Issue the commands to select the specified tablespace as the current one
+ * in the target database.
+ */
+static void
+_selectTablespace(ArchiveHandle *AH, const char *tablespace)
+{
+ PQExpBuffer qry;
+ const char *want,
+ *have;
+
+ /* do nothing in --no-tablespaces mode */
+ if (AH->ropt->noTablespace)
+ return;
+
+ have = AH->currTablespace;
+ want = tablespace;
+
+ /* no need to do anything for non-tablespace object */
+ if (!want)
+ return;
+
+ if (have && strcmp(want, have) == 0)
+ return; /* no need to do anything */
+
+ qry = createPQExpBuffer();
+
+ if (strcmp(want, "") == 0)
+ {
+ /* We want the tablespace to be the database's default */
+ appendPQExpBufferStr(qry, "SET default_tablespace = ''");
+ }
+ else
+ {
+ /* We want an explicit tablespace */
+ appendPQExpBuffer(qry, "SET default_tablespace = %s", fmtId(want));
+ }
+
+ if (RestoringToDB(AH))
+ {
+ PGresult *res;
+
+ res = PQexec(AH->connection, qry->data);
+
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+ warn_or_exit_horribly(AH, modulename,
+ "could not set default_tablespace to %s: %s",
+ fmtId(want), PQerrorMessage(AH->connection));
+
+ PQclear(res);
+ }
+ else
+ ahprintf(AH, "%s;\n\n", qry->data);
+
+ if (AH->currTablespace)
+ free(AH->currTablespace);
+ AH->currTablespace = pg_strdup(want);
+
+ destroyPQExpBuffer(qry);
+}
+
+/*
+ * Extract an object description for a TOC entry, and append it to buf.
+ *
+ * This is used for ALTER ... OWNER TO.
+ */
+static void
+_getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
+{
+ const char *type = te->desc;
+
+ /* Use ALTER TABLE for views and sequences */
+ if (strcmp(type, "VIEW") == 0 || strcmp(type, "SEQUENCE") == 0 ||
+ strcmp(type, "MATERIALIZED VIEW") == 0)
+ type = "TABLE";
+
+ /* objects that don't require special decoration */
+ if (strcmp(type, "COLLATION") == 0 ||
+ strcmp(type, "CONVERSION") == 0 ||
+ strcmp(type, "DOMAIN") == 0 ||
+ strcmp(type, "TABLE") == 0 ||
+ strcmp(type, "TYPE") == 0 ||
+ strcmp(type, "FOREIGN TABLE") == 0 ||
+ strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
+ strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
+ /* non-schema-specified objects */
+ strcmp(type, "DATABASE") == 0 ||
+ strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
+ strcmp(type, "SCHEMA") == 0 ||
+ strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
+ strcmp(type, "SERVER") == 0 ||
+ strcmp(type, "USER MAPPING") == 0)
+ {
+ /* We already know that search_path was set properly */
+ appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
+ return;
+ }
+
+ /* BLOBs just have a name, but it's numeric so must not use fmtId */
+ if (strcmp(type, "BLOB") == 0)
+ {
+ appendPQExpBuffer(buf, "LARGE OBJECT %s", te->tag);
+ return;
+ }
+
+ /*
+ * These object types require additional decoration. Fortunately, the
+ * information needed is exactly what's in the DROP command.
+ */
+ if (strcmp(type, "AGGREGATE") == 0 ||
+ strcmp(type, "FUNCTION") == 0 ||
+ strcmp(type, "OPERATOR") == 0 ||
+ strcmp(type, "OPERATOR CLASS") == 0 ||
+ strcmp(type, "OPERATOR FAMILY") == 0)
+ {
+ /* Chop "DROP " off the front and make a modifiable copy */
+ char *first = pg_strdup(te->dropStmt + 5);
+ char *last;
+
+ /* point to last character in string */
+ last = first + strlen(first) - 1;
+
+ /* Strip off any ';' or '\n' at the end */
+ while (last >= first && (*last == '\n' || *last == ';'))
+ last--;
+ *(last + 1) = '\0';
+
+ appendPQExpBufferStr(buf, first);
+
+ free(first);
+ return;
+ }
+
+ write_msg(modulename, "WARNING: don't know how to set owner for object type %s\n",
+ type);
+}
+
+static void
+_printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isData, bool acl_pass)
+{
+ /* ACLs are dumped only during acl pass */
+ if (acl_pass)
+ {
+ if (!_tocEntryIsACL(te))
+ return;
+ }
+ else
+ {
+ if (_tocEntryIsACL(te))
+ return;
+ }
+
+ /*
+ * Avoid dumping the public schema, as it will already be created ...
+ * unless we are using --clean mode, in which case it's been deleted and
+ * we'd better recreate it. Likewise for its comment, if any.
+ */
+ if (!ropt->dropSchema)
+ {
+ if (strcmp(te->desc, "SCHEMA") == 0 &&
+ strcmp(te->tag, "public") == 0)
+ return;
+ /* The comment restore would require super-user privs, so avoid it. */
+ if (strcmp(te->desc, "COMMENT") == 0 &&
+ strcmp(te->tag, "SCHEMA public") == 0)
+ return;
+ }
+
+ /* Select owner, schema, and tablespace as necessary */
+ _becomeOwner(AH, te);
+ _selectOutputSchema(AH, te->namespace);
+ _selectTablespace(AH, te->tablespace);
+
+ /* Set up OID mode too */
+ if (strcmp(te->desc, "TABLE") == 0)
+ _setWithOids(AH, te);
+
+ /* Emit header comment for item */
+ if (!AH->noTocComments)
+ {
+ const char *pfx;
+ char *sanitized_name;
+ char *sanitized_schema;
+ char *sanitized_owner;
+
+ if (isData)
+ pfx = "Data for ";
+ else
+ pfx = "";
+
+ ahprintf(AH, "--\n");
+ if (AH->public.verbose)
+ {
+ ahprintf(AH, "-- TOC entry %d (class %u OID %u)\n",
+ te->dumpId, te->catalogId.tableoid, te->catalogId.oid);
+ if (te->nDeps > 0)
+ {
+ int i;
+
+ ahprintf(AH, "-- Dependencies:");
+ for (i = 0; i < te->nDeps; i++)
+ ahprintf(AH, " %d", te->dependencies[i]);
+ ahprintf(AH, "\n");
+ }
+ }
+
+ /*
+ * Zap any line endings embedded in user-supplied fields, to prevent
+ * corruption of the dump (which could, in the worst case, present an
+ * SQL injection vulnerability if someone were to incautiously load a
+ * dump containing objects with maliciously crafted names).
+ */
+ sanitized_name = replace_line_endings(te->tag);
+ if (te->namespace)
+ sanitized_schema = replace_line_endings(te->namespace);
+ else
+ sanitized_schema = pg_strdup("-");
+ if (!ropt->noOwner)
+ sanitized_owner = replace_line_endings(te->owner);
+ else
+ sanitized_owner = pg_strdup("-");
+
+ ahprintf(AH, "-- %sName: %s; Type: %s; Schema: %s; Owner: %s",
+ pfx, sanitized_name, te->desc, sanitized_schema,
+ sanitized_owner);
+
+ free(sanitized_name);
+ free(sanitized_schema);
+ free(sanitized_owner);
+
+ if (te->tablespace && !ropt->noTablespace)
+ {
+ char *sanitized_tablespace;
+
+ sanitized_tablespace = replace_line_endings(te->tablespace);
+ ahprintf(AH, "; Tablespace: %s", sanitized_tablespace);
+ free(sanitized_tablespace);
+ }
+ ahprintf(AH, "\n");
+
+ if (AH->PrintExtraTocPtr !=NULL)
+ (*AH->PrintExtraTocPtr) (AH, te);
+ ahprintf(AH, "--\n\n");
+ }
+
+ /*
+ * Actually print the definition.
+ *
+ * Really crude hack for suppressing AUTHORIZATION clause that old pg_dump
+ * versions put into CREATE SCHEMA. We have to do this when --no-owner
+ * mode is selected. This is ugly, but I see no other good way ...
+ */
+ if (ropt->noOwner && strcmp(te->desc, "SCHEMA") == 0)
+ {
+ ahprintf(AH, "CREATE SCHEMA %s;\n\n\n", fmtId(te->tag));
+ }
+ else
+ {
+ if (strlen(te->defn) > 0)
+ ahprintf(AH, "%s\n\n", te->defn);
+ }
+
+ /*
+ * If we aren't using SET SESSION AUTH to determine ownership, we must
+ * instead issue an ALTER OWNER command. We assume that anything without
+ * a DROP command is not a separately ownable object. All the categories
+ * with DROP commands must appear in one list or the other.
+ */
+ if (!ropt->noOwner && !ropt->use_setsessauth &&
+ strlen(te->owner) > 0 && strlen(te->dropStmt) > 0)
+ {
+ if (strcmp(te->desc, "AGGREGATE") == 0 ||
+ strcmp(te->desc, "BLOB") == 0 ||
+ strcmp(te->desc, "COLLATION") == 0 ||
+ strcmp(te->desc, "CONVERSION") == 0 ||
+ strcmp(te->desc, "DATABASE") == 0 ||
+ strcmp(te->desc, "DOMAIN") == 0 ||
+ strcmp(te->desc, "FUNCTION") == 0 ||
+ strcmp(te->desc, "OPERATOR") == 0 ||
+ strcmp(te->desc, "OPERATOR CLASS") == 0 ||
+ strcmp(te->desc, "OPERATOR FAMILY") == 0 ||
+ strcmp(te->desc, "PROCEDURAL LANGUAGE") == 0 ||
+ strcmp(te->desc, "SCHEMA") == 0 ||
+ strcmp(te->desc, "TABLE") == 0 ||
+ strcmp(te->desc, "TYPE") == 0 ||
+ strcmp(te->desc, "VIEW") == 0 ||
+ strcmp(te->desc, "MATERIALIZED VIEW") == 0 ||
+ strcmp(te->desc, "SEQUENCE") == 0 ||
+ strcmp(te->desc, "FOREIGN TABLE") == 0 ||
+ strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
+ strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
+ strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
+ strcmp(te->desc, "SERVER") == 0)
+ {
+ PQExpBuffer temp = createPQExpBuffer();
+
+ appendPQExpBufferStr(temp, "ALTER ");
+ _getObjectDescription(temp, te, AH);
+ appendPQExpBuffer(temp, " OWNER TO %s;", fmtId(te->owner));
+ ahprintf(AH, "%s\n\n", temp->data);
+ destroyPQExpBuffer(temp);
+ }
+ else if (strcmp(te->desc, "CAST") == 0 ||
+ strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "CONSTRAINT") == 0 ||
+ strcmp(te->desc, "DEFAULT") == 0 ||
+ strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "INDEX") == 0 ||
+ strcmp(te->desc, "RULE") == 0 ||
+ strcmp(te->desc, "TRIGGER") == 0 ||
+ strcmp(te->desc, "USER MAPPING") == 0)
+ {
+ /* these object types don't have separate owners */
+ }
+ else
+ {
+ write_msg(modulename, "WARNING: don't know how to set owner for object type %s\n",
+ te->desc);
+ }
+ }
+
+ /*
+ * If it's an ACL entry, it might contain SET SESSION AUTHORIZATION
+ * commands, so we can no longer assume we know the current auth setting.
+ */
+ if (acl_pass)
+ {
+ if (AH->currUser)
+ free(AH->currUser);
+ AH->currUser = NULL;
+ }
+}
+
+/*
+ * Sanitize a string to be included in an SQL comment, by replacing any
+ * newlines with spaces.
+ */
+static char *
+replace_line_endings(const char *str)
+{
+ char *result;
+ char *s;
+
+ result = pg_strdup(str);
+
+ for (s = result; *s != '\0'; s++)
+ {
+ if (*s == '\n' || *s == '\r')
+ *s = ' ';
+ }
+
+ return result;
+}
+
+void
+WriteHead(ArchiveHandle *AH)
+{
+ struct tm crtm;
+
+ (*AH->WriteBufPtr) (AH, "PGDMP", 5); /* Magic code */
+ (*AH->WriteBytePtr) (AH, AH->vmaj);
+ (*AH->WriteBytePtr) (AH, AH->vmin);
+ (*AH->WriteBytePtr) (AH, AH->vrev);
+ (*AH->WriteBytePtr) (AH, AH->intSize);
+ (*AH->WriteBytePtr) (AH, AH->offSize);
+ (*AH->WriteBytePtr) (AH, AH->format);
+
+#ifndef HAVE_LIBZ
+ if (AH->compression != 0)
+ write_msg(modulename, "WARNING: requested compression not available in this "
+ "installation -- archive will be uncompressed\n");
+
+ AH->compression = 0;
+#endif
+
+ WriteInt(AH, AH->compression);
+
+ crtm = *localtime(&AH->createDate);
+ WriteInt(AH, crtm.tm_sec);
+ WriteInt(AH, crtm.tm_min);
+ WriteInt(AH, crtm.tm_hour);
+ WriteInt(AH, crtm.tm_mday);
+ WriteInt(AH, crtm.tm_mon);
+ WriteInt(AH, crtm.tm_year);
+ WriteInt(AH, crtm.tm_isdst);
+ WriteStr(AH, PQdb(AH->connection));
+ WriteStr(AH, AH->public.remoteVersionStr);
+ WriteStr(AH, PG_VERSION);
+}
+
+void
+ReadHead(ArchiveHandle *AH)
+{
+ char tmpMag[7];
+ int fmt;
+ struct tm crtm;
+
+ /*
+ * If we haven't already read the header, do so.
+ *
+ * NB: this code must agree with _discoverArchiveFormat(). Maybe find a
+ * way to unify the cases?
+ */
+ if (!AH->readHeader)
+ {
+ (*AH->ReadBufPtr) (AH, tmpMag, 5);
+
+ if (strncmp(tmpMag, "PGDMP", 5) != 0)
+ exit_horribly(modulename, "did not find magic string in file header\n");
+
+ AH->vmaj = (*AH->ReadBytePtr) (AH);
+ AH->vmin = (*AH->ReadBytePtr) (AH);
+
+ if (AH->vmaj > 1 || ((AH->vmaj == 1) && (AH->vmin > 0))) /* Version > 1.0 */
+ AH->vrev = (*AH->ReadBytePtr) (AH);
+ else
+ AH->vrev = 0;
+
+ AH->version = ((AH->vmaj * 256 + AH->vmin) * 256 + AH->vrev) * 256 + 0;
+
+ if (AH->version < K_VERS_1_0 || AH->version > K_VERS_MAX)
+ exit_horribly(modulename, "unsupported version (%d.%d) in file header\n",
+ AH->vmaj, AH->vmin);
+
+ AH->intSize = (*AH->ReadBytePtr) (AH);
+ if (AH->intSize > 32)
+ exit_horribly(modulename, "sanity check on integer size (%lu) failed\n",
+ (unsigned long) AH->intSize);
+
+ if (AH->intSize > sizeof(int))
+ write_msg(modulename, "WARNING: archive was made on a machine with larger integers, some operations might fail\n");
+
+ if (AH->version >= K_VERS_1_7)
+ AH->offSize = (*AH->ReadBytePtr) (AH);
+ else
+ AH->offSize = AH->intSize;
+
+ fmt = (*AH->ReadBytePtr) (AH);
+
+ if (AH->format != fmt)
+ exit_horribly(modulename, "expected format (%d) differs from format found in file (%d)\n",
+ AH->format, fmt);
+ }
+
+ if (AH->version >= K_VERS_1_2)
+ {
+ if (AH->version < K_VERS_1_4)
+ AH->compression = (*AH->ReadBytePtr) (AH);
+ else
+ AH->compression = ReadInt(AH);
+ }
+ else
+ AH->compression = Z_DEFAULT_COMPRESSION;
+
+#ifndef HAVE_LIBZ
+ if (AH->compression != 0)
+ write_msg(modulename, "WARNING: archive is compressed, but this installation does not support compression -- no data will be available\n");
+#endif
+
+ if (AH->version >= K_VERS_1_4)
+ {
+ crtm.tm_sec = ReadInt(AH);
+ crtm.tm_min = ReadInt(AH);
+ crtm.tm_hour = ReadInt(AH);
+ crtm.tm_mday = ReadInt(AH);
+ crtm.tm_mon = ReadInt(AH);
+ crtm.tm_year = ReadInt(AH);
+ crtm.tm_isdst = ReadInt(AH);
+
+ AH->archdbname = ReadStr(AH);
+
+ AH->createDate = mktime(&crtm);
+
+ if (AH->createDate == (time_t) -1)
+ write_msg(modulename, "WARNING: invalid creation date in header\n");
+ }
+
+ if (AH->version >= K_VERS_1_10)
+ {
+ AH->archiveRemoteVersion = ReadStr(AH);
+ AH->archiveDumpVersion = ReadStr(AH);
+ }
+}
+
+
+/*
+ * checkSeek
+ * check to see if ftell/fseek can be performed.
+ */
+bool
+checkSeek(FILE *fp)
+{
+ pgoff_t tpos;
+
+ /*
+ * If pgoff_t is wider than long, we must have "real" fseeko and not an
+ * emulation using fseek. Otherwise report no seek capability.
+ */
+#ifndef HAVE_FSEEKO
+ if (sizeof(pgoff_t) > sizeof(long))
+ return false;
+#endif
+
+ /* Check that ftello works on this file */
+ tpos = ftello(fp);
+ if (tpos < 0)
+ return false;
+
+ /*
+ * Check that fseeko(SEEK_SET) works, too. NB: we used to try to test
+ * this with fseeko(fp, 0, SEEK_CUR). But some platforms treat that as a
+ * successful no-op even on files that are otherwise unseekable.
+ */
+ if (fseeko(fp, tpos, SEEK_SET) != 0)
+ return false;
+
+ return true;
+}
+
+
+/*
+ * dumpTimestamp
+ */
+static void
+dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim)
+{
+ char buf[256];
+
+ /*
+ * We don't print the timezone on Win32, because the names are long and
+ * localized, which means they may contain characters in various random
+ * encodings; this has been seen to cause encoding errors when reading the
+ * dump script.
+ */
+ if (strftime(buf, sizeof(buf),
+#ifndef WIN32
+ "%Y-%m-%d %H:%M:%S %Z",
+#else
+ "%Y-%m-%d %H:%M:%S",
+#endif
+ localtime(&tim)) != 0)
+ ahprintf(AH, "-- %s %s\n\n", msg, buf);
+}
+
+/*
+ * Main engine for parallel restore.
+ *
+ * Work is done in three phases.
+ * First we process all SECTION_PRE_DATA tocEntries, in a single connection,
+ * just as for a standard restore. Second we process the remaining non-ACL
+ * steps in parallel worker children (threads on Windows, processes on Unix),
+ * each of which connects separately to the database. Finally we process all
+ * the ACL entries in a single connection (that happens back in
+ * RestoreArchive).
+ */
+static void
+restore_toc_entries_prefork(ArchiveHandle *AH)
+{
+ RestoreOptions *ropt = AH->ropt;
+ bool skipped_some;
+ TocEntry *next_work_item;
+
+ ahlog(AH, 2, "entering restore_toc_entries_prefork\n");
+
+ /* Adjust dependency information */
+ fix_dependencies(AH);
+
+ /*
+ * Do all the early stuff in a single connection in the parent. There's no
+ * great point in running it in parallel, in fact it will actually run
+ * faster in a single connection because we avoid all the connection and
+ * setup overhead. Also, pre-9.2 pg_dump versions were not very good
+ * about showing all the dependencies of SECTION_PRE_DATA items, so we do
+ * not risk trying to process them out-of-order.
+ *
+ * Note: as of 9.2, it should be guaranteed that all PRE_DATA items appear
+ * before DATA items, and all DATA items before POST_DATA items. That is
+ * not certain to be true in older archives, though, so this loop is coded
+ * to not assume it.
+ */
+ skipped_some = false;
+ for (next_work_item = AH->toc->next; next_work_item != AH->toc; next_work_item = next_work_item->next)
+ {
+ /* NB: process-or-continue logic must be the inverse of loop below */
+ if (next_work_item->section != SECTION_PRE_DATA)
+ {
+ /* DATA and POST_DATA items are just ignored for now */
+ if (next_work_item->section == SECTION_DATA ||
+ next_work_item->section == SECTION_POST_DATA)
+ {
+ skipped_some = true;
+ continue;
+ }
+ else
+ {
+ /*
+ * SECTION_NONE items, such as comments, can be processed now
+ * if we are still in the PRE_DATA part of the archive. Once
+ * we've skipped any items, we have to consider whether the
+ * comment's dependencies are satisfied, so skip it for now.
+ */
+ if (skipped_some)
+ continue;
+ }
+ }
+
+ ahlog(AH, 1, "processing item %d %s %s\n",
+ next_work_item->dumpId,
+ next_work_item->desc, next_work_item->tag);
+
+ (void) restore_toc_entry(AH, next_work_item, ropt, false);
+
+ /* there should be no touch of ready_list here, so pass NULL */
+ reduce_dependencies(AH, next_work_item, NULL);
+ }
+
+ /*
+ * Now close parent connection in prep for parallel steps. We do this
+ * mainly to ensure that we don't exceed the specified number of parallel
+ * connections.
+ */
+ DisconnectDatabase(&AH->public);
+
+ /* blow away any transient state from the old connection */
+ if (AH->currUser)
+ free(AH->currUser);
+ AH->currUser = NULL;
+ if (AH->currSchema)
+ free(AH->currSchema);
+ AH->currSchema = NULL;
+ if (AH->currTablespace)
+ free(AH->currTablespace);
+ AH->currTablespace = NULL;
+ AH->currWithOids = -1;
+}
+
+/*
+ * Main engine for parallel restore.
+ *
+ * Work is done in three phases.
+ * First we process all SECTION_PRE_DATA tocEntries, in a single connection,
+ * just as for a standard restore. This is done in restore_toc_entries_prefork().
+ * Second we process the remaining non-ACL steps in parallel worker children
+ * (threads on Windows, processes on Unix), these fork off and set up their
+ * connections before we call restore_toc_entries_parallel_forked.
+ * Finally we process all the ACL entries in a single connection (that happens
+ * back in RestoreArchive).
+ */
+static void
+restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate,
+ TocEntry *pending_list)
+{
+ int work_status;
+ bool skipped_some;
+ TocEntry ready_list;
+ TocEntry *next_work_item;
+ int ret_child;
+
+ ahlog(AH, 2, "entering restore_toc_entries_parallel\n");
+
+ /*
+ * Initialize the lists of ready items, the list for pending items has
+ * already been initialized in the caller. After this setup, the pending
+ * list is everything that needs to be done but is blocked by one or more
+ * dependencies, while the ready list contains items that have no
+ * remaining dependencies. Note: we don't yet filter out entries that
+ * aren't going to be restored. They might participate in dependency
+ * chains connecting entries that should be restored, so we treat them as
+ * live until we actually process them.
+ */
+ par_list_header_init(&ready_list);
+ skipped_some = false;
+ for (next_work_item = AH->toc->next; next_work_item != AH->toc; next_work_item = next_work_item->next)
+ {
+ /* NB: process-or-continue logic must be the inverse of loop above */
+ if (next_work_item->section == SECTION_PRE_DATA)
+ {
+ /* All PRE_DATA items were dealt with above */
+ continue;
+ }
+ if (next_work_item->section == SECTION_DATA ||
+ next_work_item->section == SECTION_POST_DATA)
+ {
+ /* set this flag at same point that previous loop did */
+ skipped_some = true;
+ }
+ else
+ {
+ /* SECTION_NONE items must be processed if previous loop didn't */
+ if (!skipped_some)
+ continue;
+ }
+
+ if (next_work_item->depCount > 0)
+ par_list_append(pending_list, next_work_item);
+ else
+ par_list_append(&ready_list, next_work_item);
+ }
+
+ /*
+ * main parent loop
+ *
+ * Keep going until there is no worker still running AND there is no work
+ * left to be done.
+ */
+
+ ahlog(AH, 1, "entering main parallel loop\n");
+
+ while ((next_work_item = get_next_work_item(AH, &ready_list, pstate)) != NULL ||
+ !IsEveryWorkerIdle(pstate))
+ {
+ if (next_work_item != NULL)
+ {
+ /* If not to be restored, don't waste time launching a worker */
+ if ((next_work_item->reqs & (REQ_SCHEMA | REQ_DATA)) == 0 ||
+ _tocEntryIsACL(next_work_item))
+ {
+ ahlog(AH, 1, "skipping item %d %s %s\n",
+ next_work_item->dumpId,
+ next_work_item->desc, next_work_item->tag);
+
+ par_list_remove(next_work_item);
+ reduce_dependencies(AH, next_work_item, &ready_list);
+
+ continue;
+ }
+
+ ahlog(AH, 1, "launching item %d %s %s\n",
+ next_work_item->dumpId,
+ next_work_item->desc, next_work_item->tag);
+
+ par_list_remove(next_work_item);
+
+ Assert(GetIdleWorker(pstate) != NO_SLOT);
+ DispatchJobForTocEntry(AH, pstate, next_work_item, ACT_RESTORE);
+ }
+ else
+ {
+ /* at least one child is working and we have nothing ready. */
+ Assert(!IsEveryWorkerIdle(pstate));
+ }
+
+ for (;;)
+ {
+ int nTerm = 0;
+
+ /*
+ * In order to reduce dependencies as soon as possible and
+ * especially to reap the status of workers who are working on
+ * items that pending items depend on, we do a non-blocking check
+ * for ended workers first.
+ *
+ * However, if we do not have any other work items currently that
+ * workers can work on, we do not busy-loop here but instead
+ * really wait for at least one worker to terminate. Hence we call
+ * ListenToWorkers(..., ..., do_wait = true) in this case.
+ */
+ ListenToWorkers(AH, pstate, !next_work_item);
+
+ while ((ret_child = ReapWorkerStatus(pstate, &work_status)) != NO_SLOT)
+ {
+ nTerm++;
+ mark_work_done(AH, &ready_list, ret_child, work_status, pstate);
+ }
+
+ /*
+ * We need to make sure that we have an idle worker before
+ * re-running the loop. If nTerm > 0 we already have that (quick
+ * check).
+ */
+ if (nTerm > 0)
+ break;
+
+ /* if nobody terminated, explicitly check for an idle worker */
+ if (GetIdleWorker(pstate) != NO_SLOT)
+ break;
+
+ /*
+ * If we have no idle worker, read the result of one or more
+ * workers and loop the loop to call ReapWorkerStatus() on them.
+ */
+ ListenToWorkers(AH, pstate, true);
+ }
+ }
+
+ ahlog(AH, 1, "finished main parallel loop\n");
+}
+
+static void
+restore_toc_entries_postfork(ArchiveHandle *AH, TocEntry *pending_list)
+{
+ RestoreOptions *ropt = AH->ropt;
+ TocEntry *te;
+
+ ahlog(AH, 2, "entering restore_toc_entries_postfork\n");
+
+ /*
+ * Now reconnect the single parent connection.
+ */
+ ConnectDatabase((Archive *) AH, ropt->dbname,
+ ropt->pghost, ropt->pgport, ropt->username,
+ ropt->promptPassword);
+
+ _doSetFixedOutputState(AH);
+
+ /*
+ * Make sure there is no non-ACL work left due to, say, circular
+ * dependencies, or some other pathological condition. If so, do it in the
+ * single parent connection.
+ */
+ for (te = pending_list->par_next; te != pending_list; te = te->par_next)
+ {
+ ahlog(AH, 1, "processing missed item %d %s %s\n",
+ te->dumpId, te->desc, te->tag);
+ (void) restore_toc_entry(AH, te, ropt, false);
+ }
+
+ /* The ACLs will be handled back in RestoreArchive. */
+}
+
+/*
+ * Check if te1 has an exclusive lock requirement for an item that te2 also
+ * requires, whether or not te2's requirement is for an exclusive lock.
+ */
+static bool
+has_lock_conflicts(TocEntry *te1, TocEntry *te2)
+{
+ int j,
+ k;
+
+ for (j = 0; j < te1->nLockDeps; j++)
+ {
+ for (k = 0; k < te2->nDeps; k++)
+ {
+ if (te1->lockDeps[j] == te2->dependencies[k])
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/*
+ * Initialize the header of a parallel-processing list.
+ *
+ * These are circular lists with a dummy TocEntry as header, just like the
+ * main TOC list; but we use separate list links so that an entry can be in
+ * the main TOC list as well as in a parallel-processing list.
+ */
+static void
+par_list_header_init(TocEntry *l)
+{
+ l->par_prev = l->par_next = l;
+}
+
+/* Append te to the end of the parallel-processing list headed by l */
+static void
+par_list_append(TocEntry *l, TocEntry *te)
+{
+ te->par_prev = l->par_prev;
+ l->par_prev->par_next = te;
+ l->par_prev = te;
+ te->par_next = l;
+}
+
+/* Remove te from whatever parallel-processing list it's in */
+static void
+par_list_remove(TocEntry *te)
+{
+ te->par_prev->par_next = te->par_next;
+ te->par_next->par_prev = te->par_prev;
+ te->par_prev = NULL;
+ te->par_next = NULL;
+}
+
+
+/*
+ * Find the next work item (if any) that is capable of being run now.
+ *
+ * To qualify, the item must have no remaining dependencies
+ * and no requirements for locks that are incompatible with
+ * items currently running. Items in the ready_list are known to have
+ * no remaining dependencies, but we have to check for lock conflicts.
+ *
+ * Note that the returned item has *not* been removed from ready_list.
+ * The caller must do that after successfully dispatching the item.
+ *
+ * pref_non_data is for an alternative selection algorithm that gives
+ * preference to non-data items if there is already a data load running.
+ * It is currently disabled.
+ */
+static TocEntry *
+get_next_work_item(ArchiveHandle *AH, TocEntry *ready_list,
+ ParallelState *pstate)
+{
+ bool pref_non_data = false; /* or get from AH->ropt */
+ TocEntry *data_te = NULL;
+ TocEntry *te;
+ int i,
+ k;
+
+ /*
+ * Bogus heuristics for pref_non_data
+ */
+ if (pref_non_data)
+ {
+ int count = 0;
+
+ for (k = 0; k < pstate->numWorkers; k++)
+ if (pstate->parallelSlot[k].args->te != NULL &&
+ pstate->parallelSlot[k].args->te->section == SECTION_DATA)
+ count++;
+ if (pstate->numWorkers == 0 || count * 4 < pstate->numWorkers)
+ pref_non_data = false;
+ }
+
+ /*
+ * Search the ready_list until we find a suitable item.
+ */
+ for (te = ready_list->par_next; te != ready_list; te = te->par_next)
+ {
+ bool conflicts = false;
+
+ /*
+ * Check to see if the item would need exclusive lock on something
+ * that a currently running item also needs lock on, or vice versa. If
+ * so, we don't want to schedule them together.
+ */
+ for (i = 0; i < pstate->numWorkers && !conflicts; i++)
+ {
+ TocEntry *running_te;
+
+ if (pstate->parallelSlot[i].workerStatus != WRKR_WORKING)
+ continue;
+ running_te = pstate->parallelSlot[i].args->te;
+
+ if (has_lock_conflicts(te, running_te) ||
+ has_lock_conflicts(running_te, te))
+ {
+ conflicts = true;
+ break;
+ }
+ }
+
+ if (conflicts)
+ continue;
+
+ if (pref_non_data && te->section == SECTION_DATA)
+ {
+ if (data_te == NULL)
+ data_te = te;
+ continue;
+ }
+
+ /* passed all tests, so this item can run */
+ return te;
+ }
+
+ if (data_te != NULL)
+ return data_te;
+
+ ahlog(AH, 2, "no item ready\n");
+ return NULL;
+}
+
+
+/*
+ * Restore a single TOC item in parallel with others
+ *
+ * this is run in the worker, i.e. in a thread (Windows) or a separate process
+ * (everything else). A worker process executes several such work items during
+ * a parallel backup or restore. Once we terminate here and report back that
+ * our work is finished, the master process will assign us a new work item.
+ */
+int
+parallel_restore(ParallelArgs *args)
+{
+ ArchiveHandle *AH = args->AH;
+ TocEntry *te = args->te;
+ RestoreOptions *ropt = AH->ropt;
+ int status;
+
+ _doSetFixedOutputState(AH);
+
+ Assert(AH->connection != NULL);
+
+ AH->public.n_errors = 0;
+
+ /* Restore the TOC item */
+ status = restore_toc_entry(AH, te, ropt, true);
+
+ return status;
+}
+
+
+/*
+ * Housekeeping to be done after a step has been parallel restored.
+ *
+ * Clear the appropriate slot, free all the extra memory we allocated,
+ * update status, and reduce the dependency count of any dependent items.
+ */
+static void
+mark_work_done(ArchiveHandle *AH, TocEntry *ready_list,
+ int worker, int status,
+ ParallelState *pstate)
+{
+ TocEntry *te = NULL;
+
+ te = pstate->parallelSlot[worker].args->te;
+
+ if (te == NULL)
+ exit_horribly(modulename, "could not find slot of finished worker\n");
+
+ ahlog(AH, 1, "finished item %d %s %s\n",
+ te->dumpId, te->desc, te->tag);
+
+ if (status == WORKER_CREATE_DONE)
+ mark_create_done(AH, te);
+ else if (status == WORKER_INHIBIT_DATA)
+ {
+ inhibit_data_for_failed_table(AH, te);
+ AH->public.n_errors++;
+ }
+ else if (status == WORKER_IGNORED_ERRORS)
+ AH->public.n_errors++;
+ else if (status != 0)
+ exit_horribly(modulename, "worker process failed: exit code %d\n",
+ status);
+
+ reduce_dependencies(AH, te, ready_list);
+}
+
+
+/*
+ * Process the dependency information into a form useful for parallel restore.
+ *
+ * This function takes care of fixing up some missing or badly designed
+ * dependencies, and then prepares subsidiary data structures that will be
+ * used in the main parallel-restore logic, including:
+ * 1. We build the revDeps[] arrays of incoming dependency dumpIds.
+ * 2. We set up depCount fields that are the number of as-yet-unprocessed
+ * dependencies for each TOC entry.
+ *
+ * We also identify locking dependencies so that we can avoid trying to
+ * schedule conflicting items at the same time.
+ */
+static void
+fix_dependencies(ArchiveHandle *AH)
+{
+ TocEntry *te;
+ int i;
+
+ /*
+ * Initialize the depCount/revDeps/nRevDeps fields, and make sure the TOC
+ * items are marked as not being in any parallel-processing list.
+ */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ te->depCount = te->nDeps;
+ te->revDeps = NULL;
+ te->nRevDeps = 0;
+ te->par_prev = NULL;
+ te->par_next = NULL;
+ }
+
+ /*
+ * POST_DATA items that are shown as depending on a table need to be
+ * re-pointed to depend on that table's data, instead. This ensures they
+ * won't get scheduled until the data has been loaded.
+ */
+ repoint_table_dependencies(AH);
+
+ /*
+ * Pre-8.4 versions of pg_dump neglected to set up a dependency from BLOB
+ * COMMENTS to BLOBS. Cope. (We assume there's only one BLOBS and only
+ * one BLOB COMMENTS in such files.)
+ */
+ if (AH->version < K_VERS_1_11)
+ {
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if (strcmp(te->desc, "BLOB COMMENTS") == 0 && te->nDeps == 0)
+ {
+ TocEntry *te2;
+
+ for (te2 = AH->toc->next; te2 != AH->toc; te2 = te2->next)
+ {
+ if (strcmp(te2->desc, "BLOBS") == 0)
+ {
+ te->dependencies = (DumpId *) pg_malloc(sizeof(DumpId));
+ te->dependencies[0] = te2->dumpId;
+ te->nDeps++;
+ te->depCount++;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /*
+ * At this point we start to build the revDeps reverse-dependency arrays,
+ * so all changes of dependencies must be complete.
+ */
+
+ /*
+ * Count the incoming dependencies for each item. Also, it is possible
+ * that the dependencies list items that are not in the archive at all
+ * (that should not happen in 9.2 and later, but is highly likely in older
+ * archives). Subtract such items from the depCounts.
+ */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ for (i = 0; i < te->nDeps; i++)
+ {
+ DumpId depid = te->dependencies[i];
+
+ if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL)
+ AH->tocsByDumpId[depid]->nRevDeps++;
+ else
+ te->depCount--;
+ }
+ }
+
+ /*
+ * Allocate space for revDeps[] arrays, and reset nRevDeps so we can use
+ * it as a counter below.
+ */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if (te->nRevDeps > 0)
+ te->revDeps = (DumpId *) pg_malloc(te->nRevDeps * sizeof(DumpId));
+ te->nRevDeps = 0;
+ }
+
+ /*
+ * Build the revDeps[] arrays of incoming-dependency dumpIds. This had
+ * better agree with the loops above.
+ */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ for (i = 0; i < te->nDeps; i++)
+ {
+ DumpId depid = te->dependencies[i];
+
+ if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL)
+ {
+ TocEntry *otherte = AH->tocsByDumpId[depid];
+
+ otherte->revDeps[otherte->nRevDeps++] = te->dumpId;
+ }
+ }
+ }
+
+ /*
+ * Lastly, work out the locking dependencies.
+ */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ te->lockDeps = NULL;
+ te->nLockDeps = 0;
+ identify_locking_dependencies(AH, te);
+ }
+}
+
+/*
+ * Change dependencies on table items to depend on table data items instead,
+ * but only in POST_DATA items.
+ */
+static void
+repoint_table_dependencies(ArchiveHandle *AH)
+{
+ TocEntry *te;
+ int i;
+ DumpId olddep;
+
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ if (te->section != SECTION_POST_DATA)
+ continue;
+ for (i = 0; i < te->nDeps; i++)
+ {
+ olddep = te->dependencies[i];
+ if (olddep <= AH->maxDumpId &&
+ AH->tableDataId[olddep] != 0)
+ {
+ te->dependencies[i] = AH->tableDataId[olddep];
+ ahlog(AH, 2, "transferring dependency %d -> %d to %d\n",
+ te->dumpId, olddep, AH->tableDataId[olddep]);
+ }
+ }
+ }
+}
+
+/*
+ * Identify which objects we'll need exclusive lock on in order to restore
+ * the given TOC entry (*other* than the one identified by the TOC entry
+ * itself). Record their dump IDs in the entry's lockDeps[] array.
+ */
+static void
+identify_locking_dependencies(ArchiveHandle *AH, TocEntry *te)
+{
+ DumpId *lockids;
+ int nlockids;
+ int i;
+
+ /* Quick exit if no dependencies at all */
+ if (te->nDeps == 0)
+ return;
+
+ /* Exit if this entry doesn't need exclusive lock on other objects */
+ if (!(strcmp(te->desc, "CONSTRAINT") == 0 ||
+ strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "RULE") == 0 ||
+ strcmp(te->desc, "TRIGGER") == 0))
+ return;
+
+ /*
+ * We assume the entry requires exclusive lock on each TABLE or TABLE DATA
+ * item listed among its dependencies. Originally all of these would have
+ * been TABLE items, but repoint_table_dependencies would have repointed
+ * them to the TABLE DATA items if those are present (which they might not
+ * be, eg in a schema-only dump). Note that all of the entries we are
+ * processing here are POST_DATA; otherwise there might be a significant
+ * difference between a dependency on a table and a dependency on its
+ * data, so that closer analysis would be needed here.
+ */
+ lockids = (DumpId *) pg_malloc(te->nDeps * sizeof(DumpId));
+ nlockids = 0;
+ for (i = 0; i < te->nDeps; i++)
+ {
+ DumpId depid = te->dependencies[i];
+
+ if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL &&
+ ((strcmp(AH->tocsByDumpId[depid]->desc, "TABLE DATA") == 0) ||
+ strcmp(AH->tocsByDumpId[depid]->desc, "TABLE") == 0))
+ lockids[nlockids++] = depid;
+ }
+
+ if (nlockids == 0)
+ {
+ free(lockids);
+ return;
+ }
+
+ te->lockDeps = pg_realloc(lockids, nlockids * sizeof(DumpId));
+ te->nLockDeps = nlockids;
+}
+
+/*
+ * Remove the specified TOC entry from the depCounts of items that depend on
+ * it, thereby possibly making them ready-to-run. Any pending item that
+ * becomes ready should be moved to the ready list.
+ */
+static void
+reduce_dependencies(ArchiveHandle *AH, TocEntry *te, TocEntry *ready_list)
+{
+ int i;
+
+ ahlog(AH, 2, "reducing dependencies for %d\n", te->dumpId);
+
+ for (i = 0; i < te->nRevDeps; i++)
+ {
+ TocEntry *otherte = AH->tocsByDumpId[te->revDeps[i]];
+
+ otherte->depCount--;
+ if (otherte->depCount == 0 && otherte->par_prev != NULL)
+ {
+ /* It must be in the pending list, so remove it ... */
+ par_list_remove(otherte);
+ /* ... and add to ready_list */
+ par_list_append(ready_list, otherte);
+ }
+ }
+}
+
+/*
+ * Set the created flag on the DATA member corresponding to the given
+ * TABLE member
+ */
+static void
+mark_create_done(ArchiveHandle *AH, TocEntry *te)
+{
+ if (AH->tableDataId[te->dumpId] != 0)
+ {
+ TocEntry *ted = AH->tocsByDumpId[AH->tableDataId[te->dumpId]];
+
+ ted->created = true;
+ }
+}
+
+/*
+ * Mark the DATA member corresponding to the given TABLE member
+ * as not wanted
+ */
+static void
+inhibit_data_for_failed_table(ArchiveHandle *AH, TocEntry *te)
+{
+ ahlog(AH, 1, "table \"%s\" could not be created, will not restore its data\n",
+ te->tag);
+
+ if (AH->tableDataId[te->dumpId] != 0)
+ {
+ TocEntry *ted = AH->tocsByDumpId[AH->tableDataId[te->dumpId]];
+
+ ted->reqs = 0;
+ }
+}
+
+/*
+ * Clone and de-clone routines used in parallel restoration.
+ *
+ * Enough of the structure is cloned to ensure that there is no
+ * conflict between different threads each with their own clone.
+ */
+ArchiveHandle *
+CloneArchive(ArchiveHandle *AH)
+{
+ ArchiveHandle *clone;
+
+ /* Make a "flat" copy */
+ clone = (ArchiveHandle *) pg_malloc(sizeof(ArchiveHandle));
+ memcpy(clone, AH, sizeof(ArchiveHandle));
+
+ /* Handle format-independent fields */
+ memset(&(clone->sqlparse), 0, sizeof(clone->sqlparse));
+
+ /* The clone will have its own connection, so disregard connection state */
+ clone->connection = NULL;
+ clone->currUser = NULL;
+ clone->currSchema = NULL;
+ clone->currTablespace = NULL;
+ clone->currWithOids = -1;
+
+ /* savedPassword must be local in case we change it while connecting */
+ if (clone->savedPassword)
+ clone->savedPassword = pg_strdup(clone->savedPassword);
+
+ /* clone has its own error count, too */
+ clone->public.n_errors = 0;
+
+ /*
+ * Connect our new clone object to the database: In parallel restore the
+ * parent is already disconnected, because we can connect the worker
+ * processes independently to the database (no snapshot sync required). In
+ * parallel backup we clone the parent's existing connection.
+ */
+ if (AH->mode == archModeRead)
+ {
+ RestoreOptions *ropt = AH->ropt;
+
+ Assert(AH->connection == NULL);
+ /* this also sets clone->connection */
+ ConnectDatabase((Archive *) clone, ropt->dbname,
+ ropt->pghost, ropt->pgport, ropt->username,
+ ropt->promptPassword);
+ }
+ else
+ {
+ char *dbname;
+ char *pghost;
+ char *pgport;
+ char *username;
+ const char *encname;
+
+ Assert(AH->connection != NULL);
+
+ /*
+ * Even though we are technically accessing the parent's database
+ * object here, these functions are fine to be called like that
+ * because all just return a pointer and do not actually send/receive
+ * any data to/from the database.
+ */
+ dbname = PQdb(AH->connection);
+ pghost = PQhost(AH->connection);
+ pgport = PQport(AH->connection);
+ username = PQuser(AH->connection);
+ encname = pg_encoding_to_char(AH->public.encoding);
+
+ /* this also sets clone->connection */
+ ConnectDatabase((Archive *) clone, dbname, pghost, pgport, username, TRI_NO);
+
+ /*
+ * Set the same encoding, whatever we set here is what we got from
+ * pg_encoding_to_char(), so we really shouldn't run into an error
+ * setting that very same value. Also see the comment in
+ * SetupConnection().
+ */
+ PQsetClientEncoding(clone->connection, encname);
+ }
+
+ /* Let the format-specific code have a chance too */
+ (clone->ClonePtr) (clone);
+
+ Assert(clone->connection != NULL);
+ return clone;
+}
+
+/*
+ * Release clone-local storage.
+ *
+ * Note: we assume any clone-local connection was already closed.
+ */
+void
+DeCloneArchive(ArchiveHandle *AH)
+{
+ /* Clear format-specific state */
+ (AH->DeClonePtr) (AH);
+
+ /* Clear state allocated by CloneArchive */
+ if (AH->sqlparse.curCmd)
+ destroyPQExpBuffer(AH->sqlparse.curCmd);
+
+ /* Clear any connection-local state */
+ if (AH->currUser)
+ free(AH->currUser);
+ if (AH->currSchema)
+ free(AH->currSchema);
+ if (AH->currTablespace)
+ free(AH->currTablespace);
+ if (AH->savedPassword)
+ free(AH->savedPassword);
+
+ free(AH);
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_archiver.h
@@ -0,0 +1,441 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_archiver.h
+ *
+ * Private interface to the pg_dump archiver routines.
+ * It is NOT intended that these routines be called by any
+ * dumper directly.
+ *
+ * See the headers to pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_archiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef __PG_BACKUP_ARCHIVE__
+#define __PG_BACKUP_ARCHIVE__
+
+#include "compat.h"
+
+#include "postgres_fe.h"
+
+#include <time.h>
+
+#include "pg_backup.h"
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+
+#define LOBBUFSIZE 16384
+
+/*
+ * Note: zlib.h must be included *after* libpq-fe.h, because the latter may
+ * include ssl.h, which has a naming conflict with zlib.h.
+ */
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#define GZCLOSE(fh) gzclose(fh)
+#define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s))
+#define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s))
+#define GZEOF(fh) gzeof(fh)
+#else
+#define GZCLOSE(fh) fclose(fh)
+#define GZWRITE(p, s, n, fh) (fwrite(p, s, n, fh) * (s))
+#define GZREAD(p, s, n, fh) fread(p, s, n, fh)
+#define GZEOF(fh) feof(fh)
+/* this is just the redefinition of a libz constant */
+#define Z_DEFAULT_COMPRESSION (-1)
+
+typedef struct _z_stream
+{
+ void *next_in;
+ void *next_out;
+ size_t avail_in;
+ size_t avail_out;
+} z_stream;
+typedef z_stream *z_streamp;
+#endif
+
+/* Current archive version number (the format we can output) */
+#define K_VERS_MAJOR 1
+#define K_VERS_MINOR 12
+#define K_VERS_REV 0
+
+/* Data block types */
+#define BLK_DATA 1
+#define BLK_BLOBS 3
+
+/* Historical version numbers (checked in code) */
+#define K_VERS_1_0 (( (1 * 256 + 0) * 256 + 0) * 256 + 0)
+#define K_VERS_1_2 (( (1 * 256 + 2) * 256 + 0) * 256 + 0) /* Allow No ZLIB */
+#define K_VERS_1_3 (( (1 * 256 + 3) * 256 + 0) * 256 + 0) /* BLOBs */
+#define K_VERS_1_4 (( (1 * 256 + 4) * 256 + 0) * 256 + 0) /* Date & name in header */
+#define K_VERS_1_5 (( (1 * 256 + 5) * 256 + 0) * 256 + 0) /* Handle dependencies */
+#define K_VERS_1_6 (( (1 * 256 + 6) * 256 + 0) * 256 + 0) /* Schema field in TOCs */
+#define K_VERS_1_7 (( (1 * 256 + 7) * 256 + 0) * 256 + 0) /* File Offset size in
+ * header */
+#define K_VERS_1_8 (( (1 * 256 + 8) * 256 + 0) * 256 + 0) /* change interpretation
+ * of ID numbers and
+ * dependencies */
+#define K_VERS_1_9 (( (1 * 256 + 9) * 256 + 0) * 256 + 0) /* add default_with_oids
+ * tracking */
+#define K_VERS_1_10 (( (1 * 256 + 10) * 256 + 0) * 256 + 0) /* add tablespace */
+#define K_VERS_1_11 (( (1 * 256 + 11) * 256 + 0) * 256 + 0) /* add toc section
+ * indicator */
+#define K_VERS_1_12 (( (1 * 256 + 12) * 256 + 0) * 256 + 0) /* add separate BLOB
+ * entries */
+
+/* Newest format we can read */
+#define K_VERS_MAX (( (1 * 256 + 12) * 256 + 255) * 256 + 0)
+
+
+/* Flags to indicate disposition of offsets stored in files */
+#define K_OFFSET_POS_NOT_SET 1
+#define K_OFFSET_POS_SET 2
+#define K_OFFSET_NO_DATA 3
+
+/*
+ * Special exit values from worker children. We reserve 0 for normal
+ * success; 1 and other small values should be interpreted as crashes.
+ */
+#define WORKER_OK 0
+#define WORKER_CREATE_DONE 10
+#define WORKER_INHIBIT_DATA 11
+#define WORKER_IGNORED_ERRORS 12
+
+struct _archiveHandle;
+struct _tocEntry;
+struct _restoreList;
+struct ParallelArgs;
+struct ParallelState;
+
+#define READ_ERROR_EXIT(fd) \
+ do { \
+ if (feof(fd)) \
+ exit_horribly(modulename, \
+ "could not read from input file: end of file\n"); \
+ else \
+ exit_horribly(modulename, \
+ "could not read from input file: %s\n", strerror(errno)); \
+ } while (0)
+
+#define WRITE_ERROR_EXIT \
+ do { \
+ exit_horribly(modulename, "could not write to output file: %s\n", \
+ strerror(errno)); \
+ } while (0)
+
+typedef enum T_Action
+{
+ ACT_DUMP,
+ ACT_RESTORE
+} T_Action;
+
+typedef void (*ClosePtr) (struct _archiveHandle * AH);
+typedef void (*ReopenPtr) (struct _archiveHandle * AH);
+typedef void (*ArchiveEntryPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+
+typedef void (*StartDataPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef void (*WriteDataPtr) (struct _archiveHandle * AH, const void *data, size_t dLen);
+typedef void (*EndDataPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+
+typedef void (*StartBlobsPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef void (*StartBlobPtr) (struct _archiveHandle * AH, struct _tocEntry * te, Oid oid);
+typedef void (*EndBlobPtr) (struct _archiveHandle * AH, struct _tocEntry * te, Oid oid);
+typedef void (*EndBlobsPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+
+typedef int (*WriteBytePtr) (struct _archiveHandle * AH, const int i);
+typedef int (*ReadBytePtr) (struct _archiveHandle * AH);
+typedef void (*WriteBufPtr) (struct _archiveHandle * AH, const void *c, size_t len);
+typedef void (*ReadBufPtr) (struct _archiveHandle * AH, void *buf, size_t len);
+typedef void (*SaveArchivePtr) (struct _archiveHandle * AH);
+typedef void (*WriteExtraTocPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef void (*ReadExtraTocPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef void (*PrintExtraTocPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef void (*PrintTocDataPtr) (struct _archiveHandle * AH, struct _tocEntry * te, RestoreOptions *ropt);
+
+typedef void (*ClonePtr) (struct _archiveHandle * AH);
+typedef void (*DeClonePtr) (struct _archiveHandle * AH);
+
+typedef char *(*WorkerJobRestorePtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef char *(*WorkerJobDumpPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
+typedef char *(*MasterStartParallelItemPtr) (struct _archiveHandle * AH, struct _tocEntry * te,
+ T_Action act);
+typedef int (*MasterEndParallelItemPtr) (struct _archiveHandle * AH, struct _tocEntry * te,
+ const char *str, T_Action act);
+
+typedef size_t (*CustomOutPtr) (struct _archiveHandle * AH, const void *buf, size_t len);
+
+typedef enum
+{
+ SQL_SCAN = 0, /* normal */
+ SQL_IN_SINGLE_QUOTE, /* '...' literal */
+ SQL_IN_DOUBLE_QUOTE /* "..." identifier */
+} sqlparseState;
+
+typedef struct
+{
+ sqlparseState state; /* see above */
+ bool backSlash; /* next char is backslash quoted? */
+ PQExpBuffer curCmd; /* incomplete line (NULL if not created) */
+} sqlparseInfo;
+
+typedef enum
+{
+ STAGE_NONE = 0,
+ STAGE_INITIALIZING,
+ STAGE_PROCESSING,
+ STAGE_FINALIZING
+} ArchiverStage;
+
+typedef enum
+{
+ OUTPUT_SQLCMDS = 0, /* emitting general SQL commands */
+ OUTPUT_COPYDATA, /* writing COPY data */
+ OUTPUT_OTHERDATA /* writing data as INSERT commands */
+} ArchiverOutput;
+
+typedef enum
+{
+ REQ_SCHEMA = 0x01, /* want schema */
+ REQ_DATA = 0x02, /* want data */
+ REQ_SPECIAL = 0x04 /* for special TOC entries */
+} teReqs;
+
+typedef struct _archiveHandle
+{
+ Archive public; /* Public part of archive */
+ char vmaj; /* Version of file */
+ char vmin;
+ char vrev;
+ int version; /* Conveniently formatted version */
+
+ char *archiveRemoteVersion; /* When reading an archive, the
+ * version of the dumped DB */
+ char *archiveDumpVersion; /* When reading an archive, the
+ * version of the dumper */
+
+ int debugLevel; /* Used for logging (currently only by
+ * --verbose) */
+ size_t intSize; /* Size of an integer in the archive */
+ size_t offSize; /* Size of a file offset in the archive -
+ * Added V1.7 */
+ ArchiveFormat format; /* Archive format */
+
+ sqlparseInfo sqlparse; /* state for parsing INSERT data */
+
+ time_t createDate; /* Date archive created */
+
+ /*
+ * Fields used when discovering header. A format can always get the
+ * previous read bytes from here...
+ */
+ int readHeader; /* Used if file header has been read already */
+ char *lookahead; /* Buffer used when reading header to discover
+ * format */
+ size_t lookaheadSize; /* Size of allocated buffer */
+ size_t lookaheadLen; /* Length of data in lookahead */
+ pgoff_t lookaheadPos; /* Current read position in lookahead buffer */
+
+ ArchiveEntryPtr ArchiveEntryPtr; /* Called for each metadata object */
+ StartDataPtr StartDataPtr; /* Called when table data is about to be
+ * dumped */
+ WriteDataPtr WriteDataPtr; /* Called to send some table data to the
+ * archive */
+ EndDataPtr EndDataPtr; /* Called when table data dump is finished */
+ WriteBytePtr WriteBytePtr; /* Write a byte to output */
+ ReadBytePtr ReadBytePtr; /* Read a byte from an archive */
+ WriteBufPtr WriteBufPtr; /* Write a buffer of output to the archive */
+ ReadBufPtr ReadBufPtr; /* Read a buffer of input from the archive */
+ ClosePtr ClosePtr; /* Close the archive */
+ ReopenPtr ReopenPtr; /* Reopen the archive */
+ WriteExtraTocPtr WriteExtraTocPtr; /* Write extra TOC entry data
+ * associated with the current archive
+ * format */
+ ReadExtraTocPtr ReadExtraTocPtr; /* Read extr info associated with
+ * archie format */
+ PrintExtraTocPtr PrintExtraTocPtr; /* Extra TOC info for format */
+ PrintTocDataPtr PrintTocDataPtr;
+
+ StartBlobsPtr StartBlobsPtr;
+ EndBlobsPtr EndBlobsPtr;
+ StartBlobPtr StartBlobPtr;
+ EndBlobPtr EndBlobPtr;
+
+ MasterStartParallelItemPtr MasterStartParallelItemPtr;
+ MasterEndParallelItemPtr MasterEndParallelItemPtr;
+
+ SetupWorkerPtr SetupWorkerPtr;
+ WorkerJobDumpPtr WorkerJobDumpPtr;
+ WorkerJobRestorePtr WorkerJobRestorePtr;
+
+ ClonePtr ClonePtr; /* Clone format-specific fields */
+ DeClonePtr DeClonePtr; /* Clean up cloned fields */
+
+ CustomOutPtr CustomOutPtr; /* Alternative script output routine */
+
+ /* Stuff for direct DB connection */
+ char *archdbname; /* DB name *read* from archive */
+ enum trivalue promptPassword;
+ char *savedPassword; /* password for ropt->username, if known */
+ char *use_role;
+ PGconn *connection;
+ int connectToDB; /* Flag to indicate if direct DB connection is
+ * required */
+ ArchiverOutput outputKind; /* Flag for what we're currently writing */
+ bool pgCopyIn; /* Currently in libpq 'COPY IN' mode. */
+
+ int loFd; /* BLOB fd */
+ int writingBlob; /* Flag */
+ int blobCount; /* # of blobs restored */
+
+ char *fSpec; /* Archive File Spec */
+ FILE *FH; /* General purpose file handle */
+ void *OF;
+ int gzOut; /* Output file */
+
+ struct _tocEntry *toc; /* Header of circular list of TOC entries */
+ int tocCount; /* Number of TOC entries */
+ DumpId maxDumpId; /* largest DumpId among all TOC entries */
+
+ /* arrays created after the TOC list is complete: */
+ struct _tocEntry **tocsByDumpId; /* TOCs indexed by dumpId */
+ DumpId *tableDataId; /* TABLE DATA ids, indexed by table dumpId */
+
+ struct _tocEntry *currToc; /* Used when dumping data */
+ int compression; /* Compression requested on open Possible
+ * values for compression: -1
+ * Z_DEFAULT_COMPRESSION 0 COMPRESSION_NONE
+ * 1-9 levels for gzip compression */
+ ArchiveMode mode; /* File mode - r or w */
+ void *formatData; /* Header data specific to file format */
+
+ RestoreOptions *ropt; /* Used to check restore options in ahwrite
+ * etc */
+
+ /* these vars track state to avoid sending redundant SET commands */
+ char *currUser; /* current username, or NULL if unknown */
+ char *currSchema; /* current schema, or NULL */
+ char *currTablespace; /* current tablespace, or NULL */
+ bool currWithOids; /* current default_with_oids setting */
+
+ void *lo_buf;
+ size_t lo_buf_used;
+ size_t lo_buf_size;
+
+ int noTocComments;
+ ArchiverStage stage;
+ ArchiverStage lastErrorStage;
+ struct _tocEntry *currentTE;
+ struct _tocEntry *lastErrorTE;
+} ArchiveHandle;
+
+typedef struct _tocEntry
+{
+ struct _tocEntry *prev;
+ struct _tocEntry *next;
+ CatalogId catalogId;
+ DumpId dumpId;
+ teSection section;
+ bool hadDumper; /* Archiver was passed a dumper routine (used
+ * in restore) */
+ char *tag; /* index tag */
+ char *namespace; /* null or empty string if not in a schema */
+ char *tablespace; /* null if not in a tablespace; empty string
+ * means use database default */
+ char *owner;
+ bool withOids; /* Used only by "TABLE" tags */
+ char *desc;
+ char *defn;
+ char *dropStmt;
+ char *copyStmt;
+ DumpId *dependencies; /* dumpIds of objects this one depends on */
+ int nDeps; /* number of dependencies */
+
+ DataDumperPtr dataDumper; /* Routine to dump data for object */
+ void *dataDumperArg; /* Arg for above routine */
+ void *formatData; /* TOC Entry data specific to file format */
+
+ /* working state while dumping/restoring */
+ teReqs reqs; /* do we need schema and/or data of object */
+ bool created; /* set for DATA member if TABLE was created */
+
+ /* working state (needed only for parallel restore) */
+ struct _tocEntry *par_prev; /* list links for pending/ready items; */
+ struct _tocEntry *par_next; /* these are NULL if not in either list */
+ int depCount; /* number of dependencies not yet restored */
+ DumpId *revDeps; /* dumpIds of objects depending on this one */
+ int nRevDeps; /* number of such dependencies */
+ DumpId *lockDeps; /* dumpIds of objects this one needs lock on */
+ int nLockDeps; /* number of such dependencies */
+} TocEntry;
+
+extern int parallel_restore(struct ParallelArgs *args);
+extern void on_exit_close_archive(Archive *AHX);
+
+extern void warn_or_exit_horribly(ArchiveHandle *AH, const char *modulename, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
+
+extern void WriteTOC(ArchiveHandle *AH);
+extern void ReadTOC(ArchiveHandle *AH);
+extern void WriteHead(ArchiveHandle *AH);
+extern void ReadHead(ArchiveHandle *AH);
+extern void WriteToc(ArchiveHandle *AH);
+extern void ReadToc(ArchiveHandle *AH);
+extern void WriteDataChunks(ArchiveHandle *AH, struct ParallelState *pstate);
+extern void WriteDataChunksForTocEntry(ArchiveHandle *AH, TocEntry *te);
+extern ArchiveHandle *CloneArchive(ArchiveHandle *AH);
+extern void DeCloneArchive(ArchiveHandle *AH);
+
+extern teReqs TocIDRequired(ArchiveHandle *AH, DumpId id);
+TocEntry *getTocEntryByDumpId(ArchiveHandle *AH, DumpId id);
+extern bool checkSeek(FILE *fp);
+
+#define appendStringLiteralAHX(buf,str,AH) \
+ appendStringLiteral(buf, str, (AH)->public.encoding, (AH)->public.std_strings)
+
+#define appendByteaLiteralAHX(buf,str,len,AH) \
+ appendByteaLiteral(buf, str, len, (AH)->public.std_strings)
+
+/*
+ * Mandatory routines for each supported format
+ */
+
+extern size_t WriteInt(ArchiveHandle *AH, int i);
+extern int ReadInt(ArchiveHandle *AH);
+extern char *ReadStr(ArchiveHandle *AH);
+extern size_t WriteStr(ArchiveHandle *AH, const char *s);
+
+int ReadOffset(ArchiveHandle *, pgoff_t *);
+size_t WriteOffset(ArchiveHandle *, pgoff_t, int);
+
+extern void StartRestoreBlobs(ArchiveHandle *AH);
+extern void StartRestoreBlob(ArchiveHandle *AH, Oid oid, bool drop);
+extern void EndRestoreBlob(ArchiveHandle *AH, Oid oid);
+extern void EndRestoreBlobs(ArchiveHandle *AH);
+
+extern void InitArchiveFmt_Custom(ArchiveHandle *AH);
+extern void InitArchiveFmt_Null(ArchiveHandle *AH);
+extern void InitArchiveFmt_Directory(ArchiveHandle *AH);
+extern void InitArchiveFmt_Tar(ArchiveHandle *AH);
+
+extern bool isValidTarHeader(char *header);
+
+extern int ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *newUser);
+extern void DropBlobIfExists(ArchiveHandle *AH, Oid oid);
+
+void ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH);
+int ahprintf(ArchiveHandle *AH, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+
+void ahlog(ArchiveHandle *AH, int level, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
+
+#endif
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_custom.c
@@ -0,0 +1,995 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_custom.c
+ *
+ * Implements the custom output format.
+ *
+ * The comments with the routined in this code are a good place to
+ * understand how to write a new format.
+ *
+ * See the headers to pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * and any liability will be limited to the time taken to fix any
+ * related bug.
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_custom.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "compress_io.h"
+#include "parallel.h"
+#include "pg_backup_utils.h"
+
+/*--------
+ * Routines in the format interface
+ *--------
+ */
+
+static void _ArchiveEntry(ArchiveHandle *AH, TocEntry *te);
+static void _StartData(ArchiveHandle *AH, TocEntry *te);
+static void _WriteData(ArchiveHandle *AH, const void *data, size_t dLen);
+static void _EndData(ArchiveHandle *AH, TocEntry *te);
+static int _WriteByte(ArchiveHandle *AH, const int i);
+static int _ReadByte(ArchiveHandle *);
+static void _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len);
+static void _ReadBuf(ArchiveHandle *AH, void *buf, size_t len);
+static void _CloseArchive(ArchiveHandle *AH);
+static void _ReopenArchive(ArchiveHandle *AH);
+static void _PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt);
+static void _WriteExtraToc(ArchiveHandle *AH, TocEntry *te);
+static void _ReadExtraToc(ArchiveHandle *AH, TocEntry *te);
+static void _PrintExtraToc(ArchiveHandle *AH, TocEntry *te);
+
+static void _PrintData(ArchiveHandle *AH);
+static void _skipData(ArchiveHandle *AH);
+static void _skipBlobs(ArchiveHandle *AH);
+
+static void _StartBlobs(ArchiveHandle *AH, TocEntry *te);
+static void _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlobs(ArchiveHandle *AH, TocEntry *te);
+static void _LoadBlobs(ArchiveHandle *AH, bool drop);
+static void _Clone(ArchiveHandle *AH);
+static void _DeClone(ArchiveHandle *AH);
+
+static char *_MasterStartParallelItem(ArchiveHandle *AH, TocEntry *te, T_Action act);
+static int _MasterEndParallelItem(ArchiveHandle *AH, TocEntry *te, const char *str, T_Action act);
+char *_WorkerJobRestoreCustom(ArchiveHandle *AH, TocEntry *te);
+
+typedef struct
+{
+ CompressorState *cs;
+ int hasSeek;
+ pgoff_t filePos;
+ pgoff_t dataStart;
+} lclContext;
+
+typedef struct
+{
+ int dataState;
+ pgoff_t dataPos;
+} lclTocEntry;
+
+
+/*------
+ * Static declarations
+ *------
+ */
+static void _readBlockHeader(ArchiveHandle *AH, int *type, int *id);
+static pgoff_t _getFilePos(ArchiveHandle *AH, lclContext *ctx);
+
+static void _CustomWriteFunc(ArchiveHandle *AH, const char *buf, size_t len);
+static size_t _CustomReadFunc(ArchiveHandle *AH, char **buf, size_t *buflen);
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("custom archiver");
+
+
+
+/*
+ * Init routine required by ALL formats. This is a global routine
+ * and should be declared in pg_backup_archiver.h
+ *
+ * It's task is to create any extra archive context (using AH->formatData),
+ * and to initialize the supported function pointers.
+ *
+ * It should also prepare whatever it's input source is for reading/writing,
+ * and in the case of a read mode connection, it should load the Header & TOC.
+ */
+void
+InitArchiveFmt_Custom(ArchiveHandle *AH)
+{
+ lclContext *ctx;
+
+ /* Assuming static functions, this can be copied for each format. */
+ AH->ArchiveEntryPtr = _ArchiveEntry;
+ AH->StartDataPtr = _StartData;
+ AH->WriteDataPtr = _WriteData;
+ AH->EndDataPtr = _EndData;
+ AH->WriteBytePtr = _WriteByte;
+ AH->ReadBytePtr = _ReadByte;
+ AH->WriteBufPtr = _WriteBuf;
+ AH->ReadBufPtr = _ReadBuf;
+ AH->ClosePtr = _CloseArchive;
+ AH->ReopenPtr = _ReopenArchive;
+ AH->PrintTocDataPtr = _PrintTocData;
+ AH->ReadExtraTocPtr = _ReadExtraToc;
+ AH->WriteExtraTocPtr = _WriteExtraToc;
+ AH->PrintExtraTocPtr = _PrintExtraToc;
+
+ AH->StartBlobsPtr = _StartBlobs;
+ AH->StartBlobPtr = _StartBlob;
+ AH->EndBlobPtr = _EndBlob;
+ AH->EndBlobsPtr = _EndBlobs;
+ AH->ClonePtr = _Clone;
+ AH->DeClonePtr = _DeClone;
+
+ AH->MasterStartParallelItemPtr = _MasterStartParallelItem;
+ AH->MasterEndParallelItemPtr = _MasterEndParallelItem;
+
+ /* no parallel dump in the custom archive, only parallel restore */
+ AH->WorkerJobDumpPtr = NULL;
+ AH->WorkerJobRestorePtr = _WorkerJobRestoreCustom;
+
+ /* Set up a private area. */
+ ctx = (lclContext *) pg_malloc0(sizeof(lclContext));
+ AH->formatData = (void *) ctx;
+
+ /* Initialize LO buffering */
+ AH->lo_buf_size = LOBBUFSIZE;
+ AH->lo_buf = (void *) pg_malloc(LOBBUFSIZE);
+
+ ctx->filePos = 0;
+
+ /*
+ * Now open the file
+ */
+ if (AH->mode == archModeWrite)
+ {
+ if (AH->fSpec && strcmp(AH->fSpec, "") != 0)
+ {
+ AH->FH = fopen(AH->fSpec, PG_BINARY_W);
+ if (!AH->FH)
+ exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ AH->fSpec, strerror(errno));
+ }
+ else
+ {
+ AH->FH = stdout;
+ if (!AH->FH)
+ exit_horribly(modulename, "could not open output file: %s\n",
+ strerror(errno));
+ }
+
+ ctx->hasSeek = checkSeek(AH->FH);
+ }
+ else
+ {
+ if (AH->fSpec && strcmp(AH->fSpec, "") != 0)
+ {
+ AH->FH = fopen(AH->fSpec, PG_BINARY_R);
+ if (!AH->FH)
+ exit_horribly(modulename, "could not open input file \"%s\": %s\n",
+ AH->fSpec, strerror(errno));
+ }
+ else
+ {
+ AH->FH = stdin;
+ if (!AH->FH)
+ exit_horribly(modulename, "could not open input file: %s\n",
+ strerror(errno));
+ }
+
+ ctx->hasSeek = checkSeek(AH->FH);
+
+ ReadHead(AH);
+ ReadToc(AH);
+ ctx->dataStart = _getFilePos(AH, ctx);
+ }
+
+}
+
+/*
+ * Called by the Archiver when the dumper creates a new TOC entry.
+ *
+ * Optional.
+ *
+ * Set up extrac format-related TOC data.
+*/
+static void
+_ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx;
+
+ ctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ if (te->dataDumper)
+ ctx->dataState = K_OFFSET_POS_NOT_SET;
+ else
+ ctx->dataState = K_OFFSET_NO_DATA;
+
+ te->formatData = (void *) ctx;
+}
+
+/*
+ * Called by the Archiver to save any extra format-related TOC entry
+ * data.
+ *
+ * Optional.
+ *
+ * Use the Archiver routines to write data - they are non-endian, and
+ * maintain other important file information.
+ */
+static void
+_WriteExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx = (lclTocEntry *) te->formatData;
+
+ WriteOffset(AH, ctx->dataPos, ctx->dataState);
+}
+
+/*
+ * Called by the Archiver to read any extra format-related TOC data.
+ *
+ * Optional.
+ *
+ * Needs to match the order defined in _WriteExtraToc, and should also
+ * use the Archiver input routines.
+ */
+static void
+_ReadExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx = (lclTocEntry *) te->formatData;
+
+ if (ctx == NULL)
+ {
+ ctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ te->formatData = (void *) ctx;
+ }
+
+ ctx->dataState = ReadOffset(AH, &(ctx->dataPos));
+
+ /*
+ * Prior to V1.7 (pg7.3), we dumped the data size as an int now we don't
+ * dump it at all.
+ */
+ if (AH->version < K_VERS_1_7)
+ ReadInt(AH);
+}
+
+/*
+ * Called by the Archiver when restoring an archive to output a comment
+ * that includes useful information about the TOC entry.
+ *
+ * Optional.
+ *
+ */
+static void
+_PrintExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx = (lclTocEntry *) te->formatData;
+
+ if (AH->public.verbose)
+ ahprintf(AH, "-- Data Pos: " INT64_FORMAT "\n",
+ (int64) ctx->dataPos);
+}
+
+/*
+ * Called by the archiver when saving TABLE DATA (not schema). This routine
+ * should save whatever format-specific information is needed to read
+ * the archive back.
+ *
+ * It is called just prior to the dumper's 'DataDumper' routine being called.
+ *
+ * Optional, but strongly recommended.
+ *
+ */
+static void
+_StartData(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ tctx->dataPos = _getFilePos(AH, ctx);
+ tctx->dataState = K_OFFSET_POS_SET;
+
+ _WriteByte(AH, BLK_DATA); /* Block type */
+ WriteInt(AH, te->dumpId); /* For sanity check */
+
+ ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+}
+
+/*
+ * Called by archiver when dumper calls WriteData. This routine is
+ * called for both BLOB and TABLE data; it is the responsibility of
+ * the format to manage each kind of data using StartBlob/StartData.
+ *
+ * It should only be called from within a DataDumper routine.
+ *
+ * Mandatory.
+ */
+static void
+_WriteData(ArchiveHandle *AH, const void *data, size_t dLen)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ CompressorState *cs = ctx->cs;
+
+ if (dLen > 0)
+ /* WriteDataToArchive() internally throws write errors */
+ WriteDataToArchive(AH, cs, data, dLen);
+
+ return;
+}
+
+/*
+ * Called by the archiver when a dumper's 'DataDumper' routine has
+ * finished.
+ *
+ * Optional.
+ *
+ */
+static void
+_EndData(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ EndCompressor(AH, ctx->cs);
+ /* Send the end marker */
+ WriteInt(AH, 0);
+}
+
+/*
+ * Called by the archiver when starting to save all BLOB DATA (not schema).
+ * This routine should save whatever format-specific information is needed
+ * to read the BLOBs back into memory.
+ *
+ * It is called just prior to the dumper's DataDumper routine.
+ *
+ * Optional, but strongly recommended.
+ */
+static void
+_StartBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ tctx->dataPos = _getFilePos(AH, ctx);
+ tctx->dataState = K_OFFSET_POS_SET;
+
+ _WriteByte(AH, BLK_BLOBS); /* Block type */
+ WriteInt(AH, te->dumpId); /* For sanity check */
+}
+
+/*
+ * Called by the archiver when the dumper calls StartBlob.
+ *
+ * Mandatory.
+ *
+ * Must save the passed OID for retrieval at restore-time.
+ */
+static void
+_StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (oid == 0)
+ exit_horribly(modulename, "invalid OID for large object\n");
+
+ WriteInt(AH, oid);
+
+ ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+}
+
+/*
+ * Called by the archiver when the dumper calls EndBlob.
+ *
+ * Optional.
+ */
+static void
+_EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ EndCompressor(AH, ctx->cs);
+ /* Send the end marker */
+ WriteInt(AH, 0);
+}
+
+/*
+ * Called by the archiver when finishing saving all BLOB DATA.
+ *
+ * Optional.
+ */
+static void
+_EndBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ /* Write out a fake zero OID to mark end-of-blobs. */
+ WriteInt(AH, 0);
+}
+
+/*
+ * Print data for a given TOC entry
+ */
+static void
+_PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+ int blkType;
+ int id;
+
+ if (tctx->dataState == K_OFFSET_NO_DATA)
+ return;
+
+ if (!ctx->hasSeek || tctx->dataState == K_OFFSET_POS_NOT_SET)
+ {
+ /*
+ * We cannot seek directly to the desired block. Instead, skip over
+ * block headers until we find the one we want. This could fail if we
+ * are asked to restore items out-of-order.
+ */
+ _readBlockHeader(AH, &blkType, &id);
+
+ while (blkType != EOF && id != te->dumpId)
+ {
+ switch (blkType)
+ {
+ case BLK_DATA:
+ _skipData(AH);
+ break;
+
+ case BLK_BLOBS:
+ _skipBlobs(AH);
+ break;
+
+ default: /* Always have a default */
+ exit_horribly(modulename,
+ "unrecognized data block type (%d) while searching archive\n",
+ blkType);
+ break;
+ }
+ _readBlockHeader(AH, &blkType, &id);
+ }
+ }
+ else
+ {
+ /* We can just seek to the place we need to be. */
+ if (fseeko(AH->FH, tctx->dataPos, SEEK_SET) != 0)
+ exit_horribly(modulename, "error during file seek: %s\n",
+ strerror(errno));
+
+ _readBlockHeader(AH, &blkType, &id);
+ }
+
+ /* Produce suitable failure message if we fell off end of file */
+ if (blkType == EOF)
+ {
+ if (tctx->dataState == K_OFFSET_POS_NOT_SET)
+ exit_horribly(modulename, "could not find block ID %d in archive -- "
+ "possibly due to out-of-order restore request, "
+ "which cannot be handled due to lack of data offsets in archive\n",
+ te->dumpId);
+ else if (!ctx->hasSeek)
+ exit_horribly(modulename, "could not find block ID %d in archive -- "
+ "possibly due to out-of-order restore request, "
+ "which cannot be handled due to non-seekable input file\n",
+ te->dumpId);
+ else /* huh, the dataPos led us to EOF? */
+ exit_horribly(modulename, "could not find block ID %d in archive -- "
+ "possibly corrupt archive\n",
+ te->dumpId);
+ }
+
+ /* Are we sane? */
+ if (id != te->dumpId)
+ exit_horribly(modulename, "found unexpected block ID (%d) when reading data -- expected %d\n",
+ id, te->dumpId);
+
+ switch (blkType)
+ {
+ case BLK_DATA:
+ _PrintData(AH);
+ break;
+
+ case BLK_BLOBS:
+ _LoadBlobs(AH, ropt->dropSchema);
+ break;
+
+ default: /* Always have a default */
+ exit_horribly(modulename, "unrecognized data block type %d while restoring archive\n",
+ blkType);
+ break;
+ }
+}
+
+/*
+ * Print data from current file position.
+*/
+static void
+_PrintData(ArchiveHandle *AH)
+{
+ ReadDataFromArchive(AH, AH->compression, _CustomReadFunc);
+}
+
+static void
+_LoadBlobs(ArchiveHandle *AH, bool drop)
+{
+ Oid oid;
+
+ StartRestoreBlobs(AH);
+
+ oid = ReadInt(AH);
+ while (oid != 0)
+ {
+ StartRestoreBlob(AH, oid, drop);
+ _PrintData(AH);
+ EndRestoreBlob(AH, oid);
+ oid = ReadInt(AH);
+ }
+
+ EndRestoreBlobs(AH);
+}
+
+/*
+ * Skip the BLOBs from the current file position.
+ * BLOBS are written sequentially as data blocks (see below).
+ * Each BLOB is preceded by it's original OID.
+ * A zero OID indicated the end of the BLOBS
+ */
+static void
+_skipBlobs(ArchiveHandle *AH)
+{
+ Oid oid;
+
+ oid = ReadInt(AH);
+ while (oid != 0)
+ {
+ _skipData(AH);
+ oid = ReadInt(AH);
+ }
+}
+
+/*
+ * Skip data from current file position.
+ * Data blocks are formatted as an integer length, followed by data.
+ * A zero length denoted the end of the block.
+*/
+static void
+_skipData(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ size_t blkLen;
+ char *buf = NULL;
+ int buflen = 0;
+ size_t cnt;
+
+ blkLen = ReadInt(AH);
+ while (blkLen != 0)
+ {
+ if (blkLen > buflen)
+ {
+ if (buf)
+ free(buf);
+ buf = (char *) pg_malloc(blkLen);
+ buflen = blkLen;
+ }
+ if ((cnt = fread(buf, 1, blkLen, AH->FH)) != blkLen)
+ {
+ if (feof(AH->FH))
+ exit_horribly(modulename,
+ "could not read from input file: end of file\n");
+ else
+ exit_horribly(modulename,
+ "could not read from input file: %s\n", strerror(errno));
+ }
+
+ ctx->filePos += blkLen;
+
+ blkLen = ReadInt(AH);
+ }
+
+ if (buf)
+ free(buf);
+}
+
+/*
+ * Write a byte of data to the archive.
+ *
+ * Mandatory.
+ *
+ * Called by the archiver to do integer & byte output to the archive.
+ */
+static int
+_WriteByte(ArchiveHandle *AH, const int i)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ int res;
+
+ if ((res = fputc(i, AH->FH)) == EOF)
+ WRITE_ERROR_EXIT;
+ ctx->filePos += 1;
+
+ return 1;
+}
+
+/*
+ * Read a byte of data from the archive.
+ *
+ * Mandatory
+ *
+ * Called by the archiver to read bytes & integers from the archive.
+ * EOF should be treated as a fatal error.
+ */
+static int
+_ReadByte(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ int res;
+
+ res = getc(AH->FH);
+ if (res == EOF)
+ READ_ERROR_EXIT(AH->FH);
+ ctx->filePos += 1;
+ return res;
+}
+
+/*
+ * Write a buffer of data to the archive.
+ *
+ * Mandatory.
+ *
+ * Called by the archiver to write a block of bytes to the archive.
+ */
+static void
+_WriteBuf(ArchiveHandle *AH, const void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (fwrite(buf, 1, len, AH->FH) != len)
+ WRITE_ERROR_EXIT;
+ ctx->filePos += len;
+
+ return;
+}
+
+/*
+ * Read a block of bytes from the archive.
+ *
+ * Mandatory.
+ *
+ * Called by the archiver to read a block of bytes from the archive
+ */
+static void
+_ReadBuf(ArchiveHandle *AH, void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (fread(buf, 1, len, AH->FH) != len)
+ READ_ERROR_EXIT(AH->FH);
+ ctx->filePos += len;
+
+ return;
+}
+
+/*
+ * Close the archive.
+ *
+ * Mandatory.
+ *
+ * When writing the archive, this is the routine that actually starts
+ * the process of saving it to files. No data should be written prior
+ * to this point, since the user could sort the TOC after creating it.
+ *
+ * If an archive is to be written, this toutine must call:
+ * WriteHead to save the archive header
+ * WriteToc to save the TOC entries
+ * WriteDataChunks to save all DATA & BLOBs.
+ *
+ */
+static void
+_CloseArchive(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ pgoff_t tpos;
+
+ if (AH->mode == archModeWrite)
+ {
+ WriteHead(AH);
+ /* Remember TOC's seek position for use below */
+ tpos = ftello(AH->FH);
+ if (tpos < 0 && ctx->hasSeek)
+ exit_horribly(modulename, "could not determine seek position in archive file: %s\n",
+ strerror(errno));
+ WriteToc(AH);
+ ctx->dataStart = _getFilePos(AH, ctx);
+ WriteDataChunks(AH, NULL);
+
+ /*
+ * If possible, re-write the TOC in order to update the data offset
+ * information. This is not essential, as pg_restore can cope in most
+ * cases without it; but it can make pg_restore significantly faster
+ * in some situations (especially parallel restore).
+ */
+ if (ctx->hasSeek &&
+ fseeko(AH->FH, tpos, SEEK_SET) == 0)
+ WriteToc(AH);
+ }
+
+ if (fclose(AH->FH) != 0)
+ exit_horribly(modulename, "could not close archive file: %s\n", strerror(errno));
+
+ AH->FH = NULL;
+}
+
+/*
+ * Reopen the archive's file handle.
+ *
+ * We close the original file handle, except on Windows. (The difference
+ * is because on Windows, this is used within a multithreading context,
+ * and we don't want a thread closing the parent file handle.)
+ */
+static void
+_ReopenArchive(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ pgoff_t tpos;
+
+ if (AH->mode == archModeWrite)
+ exit_horribly(modulename, "can only reopen input archives\n");
+
+ /*
+ * These two cases are user-facing errors since they represent unsupported
+ * (but not invalid) use-cases. Word the error messages appropriately.
+ */
+ if (AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0)
+ exit_horribly(modulename, "parallel restore from standard input is not supported\n");
+ if (!ctx->hasSeek)
+ exit_horribly(modulename, "parallel restore from non-seekable file is not supported\n");
+
+ tpos = ftello(AH->FH);
+ if (tpos < 0)
+ exit_horribly(modulename, "could not determine seek position in archive file: %s\n",
+ strerror(errno));
+
+#ifndef WIN32
+ if (fclose(AH->FH) != 0)
+ exit_horribly(modulename, "could not close archive file: %s\n",
+ strerror(errno));
+#endif
+
+ AH->FH = fopen(AH->fSpec, PG_BINARY_R);
+ if (!AH->FH)
+ exit_horribly(modulename, "could not open input file \"%s\": %s\n",
+ AH->fSpec, strerror(errno));
+
+ if (fseeko(AH->FH, tpos, SEEK_SET) != 0)
+ exit_horribly(modulename, "could not set seek position in archive file: %s\n",
+ strerror(errno));
+}
+
+/*
+ * Clone format-specific fields during parallel restoration.
+ */
+static void
+_Clone(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ AH->formatData = (lclContext *) pg_malloc(sizeof(lclContext));
+ memcpy(AH->formatData, ctx, sizeof(lclContext));
+ ctx = (lclContext *) AH->formatData;
+
+ /* sanity check, shouldn't happen */
+ if (ctx->cs != NULL)
+ exit_horribly(modulename, "compressor active\n");
+
+ /*
+ * Note: we do not make a local lo_buf because we expect at most one BLOBS
+ * entry per archive, so no parallelism is possible. Likewise,
+ * TOC-entry-local state isn't an issue because any one TOC entry is
+ * touched by just one worker child.
+ */
+}
+
+static void
+_DeClone(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ free(ctx);
+}
+
+/*
+ * This function is executed in the child of a parallel backup for the
+ * custom format archive and dumps the actual data.
+ */
+char *
+_WorkerJobRestoreCustom(ArchiveHandle *AH, TocEntry *te)
+{
+ /*
+ * short fixed-size string + some ID so far, this needs to be malloc'ed
+ * instead of static because we work with threads on windows
+ */
+ const int buflen = 64;
+ char *buf = (char *) pg_malloc(buflen);
+ ParallelArgs pargs;
+ int status;
+
+ pargs.AH = AH;
+ pargs.te = te;
+
+ status = parallel_restore(&pargs);
+
+ snprintf(buf, buflen, "OK RESTORE %d %d %d", te->dumpId, status,
+ status == WORKER_IGNORED_ERRORS ? AH->public.n_errors : 0);
+
+ return buf;
+}
+
+/*
+ * This function is executed in the parent process. Depending on the desired
+ * action (dump or restore) it creates a string that is understood by the
+ * _WorkerJobDump /_WorkerJobRestore functions of the dump format.
+ */
+static char *
+_MasterStartParallelItem(ArchiveHandle *AH, TocEntry *te, T_Action act)
+{
+ /*
+ * A static char is okay here, even on Windows because we call this
+ * function only from one process (the master).
+ */
+ static char buf[64]; /* short fixed-size string + number */
+
+ /* no parallel dump in the custom archive format */
+ Assert(act == ACT_RESTORE);
+
+ snprintf(buf, sizeof(buf), "RESTORE %d", te->dumpId);
+
+ return buf;
+}
+
+/*
+ * This function is executed in the parent process. It analyzes the response of
+ * the _WorkerJobDump / _WorkerJobRestore functions of the dump format.
+ */
+static int
+_MasterEndParallelItem(ArchiveHandle *AH, TocEntry *te, const char *str, T_Action act)
+{
+ DumpId dumpId;
+ int nBytes,
+ status,
+ n_errors;
+
+ /* no parallel dump in the custom archive */
+ Assert(act == ACT_RESTORE);
+
+ sscanf(str, "%u %u %u%n", &dumpId, &status, &n_errors, &nBytes);
+
+ Assert(nBytes == strlen(str));
+ Assert(dumpId == te->dumpId);
+
+ AH->public.n_errors += n_errors;
+
+ return status;
+}
+
+/*--------------------------------------------------
+ * END OF FORMAT CALLBACKS
+ *--------------------------------------------------
+ */
+
+/*
+ * Get the current position in the archive file.
+ */
+static pgoff_t
+_getFilePos(ArchiveHandle *AH, lclContext *ctx)
+{
+ pgoff_t pos;
+
+ if (ctx->hasSeek)
+ {
+ /*
+ * Prior to 1.7 (pg7.3) we relied on the internally maintained
+ * pointer. Now we rely on ftello() always, unless the file has been
+ * found to not support it. For debugging purposes, print a warning
+ * if the internal pointer disagrees, so that we're more likely to
+ * notice if something's broken about the internal position tracking.
+ */
+ pos = ftello(AH->FH);
+ if (pos < 0)
+ exit_horribly(modulename, "could not determine seek position in archive file: %s\n",
+ strerror(errno));
+
+ if (pos != ctx->filePos)
+ write_msg(modulename, "WARNING: ftell mismatch with expected position -- ftell used\n");
+ }
+ else
+ pos = ctx->filePos;
+ return pos;
+}
+
+/*
+ * Read a data block header. The format changed in V1.3, so we
+ * centralize the code here for simplicity. Returns *type = EOF
+ * if at EOF.
+ */
+static void
+_readBlockHeader(ArchiveHandle *AH, int *type, int *id)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ int byt;
+
+ /*
+ * Note: if we are at EOF with a pre-1.3 input file, we'll exit_horribly
+ * inside ReadInt rather than returning EOF. It doesn't seem worth
+ * jumping through hoops to deal with that case better, because no such
+ * files are likely to exist in the wild: only some 7.1 development
+ * versions of pg_dump ever generated such files.
+ */
+ if (AH->version < K_VERS_1_3)
+ *type = BLK_DATA;
+ else
+ {
+ byt = getc(AH->FH);
+ *type = byt;
+ if (byt == EOF)
+ {
+ *id = 0; /* don't return an uninitialized value */
+ return;
+ }
+ ctx->filePos += 1;
+ }
+
+ *id = ReadInt(AH);
+}
+
+/*
+ * Callback function for WriteDataToArchive. Writes one block of (compressed)
+ * data to the archive.
+ */
+static void
+_CustomWriteFunc(ArchiveHandle *AH, const char *buf, size_t len)
+{
+ /* never write 0-byte blocks (this should not happen) */
+ if (len > 0)
+ {
+ WriteInt(AH, len);
+ _WriteBuf(AH, buf, len);
+ }
+ return;
+}
+
+/*
+ * Callback function for ReadDataFromArchive. To keep things simple, we
+ * always read one compressed block at a time.
+ */
+static size_t
+_CustomReadFunc(ArchiveHandle *AH, char **buf, size_t *buflen)
+{
+ size_t blkLen;
+
+ /* Read length */
+ blkLen = ReadInt(AH);
+ if (blkLen == 0)
+ return 0;
+
+ /* If the caller's buffer is not large enough, allocate a bigger one */
+ if (blkLen > *buflen)
+ {
+ free(*buf);
+ *buf = (char *) pg_malloc(blkLen);
+ *buflen = blkLen;
+ }
+
+ /* exits app on read errors */
+ _ReadBuf(AH, *buf, blkLen);
+
+ return blkLen;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_db.c
@@ -0,0 +1,632 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_db.c
+ *
+ * Implements the basic DB functions used by the archiver.
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_db.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "pg_backup_db.h"
+#include "pg_backup_utils.h"
+#include "dumputils.h"
+#include "parallel.h"
+
+#include <unistd.h>
+#include <ctype.h>
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+
+#define DB_MAX_ERR_STMT 128
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("archiver (db)");
+
+static void _check_database_version(ArchiveHandle *AH);
+static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser);
+static void notice_processor(void *arg, const char *message);
+
+static void
+_check_database_version(ArchiveHandle *AH)
+{
+ const char *remoteversion_str;
+ int remoteversion;
+
+ remoteversion_str = PQparameterStatus(AH->connection, "server_version");
+ remoteversion = PQserverVersion(AH->connection);
+ if (remoteversion == 0 || !remoteversion_str)
+ exit_horribly(modulename, "could not get server_version from libpq\n");
+
+ AH->public.remoteVersionStr = pg_strdup(remoteversion_str);
+ AH->public.remoteVersion = remoteversion;
+ if (!AH->archiveRemoteVersion)
+ AH->archiveRemoteVersion = AH->public.remoteVersionStr;
+
+ if (remoteversion != PG_VERSION_NUM
+ && (remoteversion < AH->public.minRemoteVersion ||
+ remoteversion > AH->public.maxRemoteVersion))
+ {
+ write_msg(NULL, "server version: %s; %s version: %s\n",
+ remoteversion_str, progname, PG_VERSION);
+ exit_horribly(NULL, "aborting because of server version mismatch\n");
+ }
+}
+
+/*
+ * Reconnect to the server. If dbname is not NULL, use that database,
+ * else the one associated with the archive handle. If username is
+ * not NULL, use that user name, else the one from the handle. If
+ * both the database and the user match the existing connection already,
+ * nothing will be done.
+ *
+ * Returns 1 in any case.
+ */
+int
+ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
+{
+ PGconn *newConn;
+ const char *newdbname;
+ const char *newusername;
+
+ if (!dbname)
+ newdbname = PQdb(AH->connection);
+ else
+ newdbname = dbname;
+
+ if (!username)
+ newusername = PQuser(AH->connection);
+ else
+ newusername = username;
+
+ /* Let's see if the request is already satisfied */
+ if (strcmp(newdbname, PQdb(AH->connection)) == 0 &&
+ strcmp(newusername, PQuser(AH->connection)) == 0)
+ return 1;
+
+ newConn = _connectDB(AH, newdbname, newusername);
+
+ PQfinish(AH->connection);
+ AH->connection = newConn;
+
+ return 1;
+}
+
+/*
+ * Connect to the db again.
+ *
+ * Note: it's not really all that sensible to use a single-entry password
+ * cache if the username keeps changing. In current usage, however, the
+ * username never does change, so one savedPassword is sufficient. We do
+ * update the cache on the off chance that the password has changed since the
+ * start of the run.
+ */
+static PGconn *
+_connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
+{
+ PGconn *newConn;
+ const char *newdb;
+ const char *newuser;
+ char *password;
+ bool new_pass;
+
+ if (!reqdb)
+ newdb = PQdb(AH->connection);
+ else
+ newdb = reqdb;
+
+ if (!requser || strlen(requser) == 0)
+ newuser = PQuser(AH->connection);
+ else
+ newuser = requser;
+
+ ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n",
+ newdb, newuser);
+
+ password = AH->savedPassword ? pg_strdup(AH->savedPassword) : NULL;
+
+ if (AH->promptPassword == TRI_YES && password == NULL)
+ {
+ password = simple_prompt("Password: ", 100, false);
+ if (password == NULL)
+ exit_horribly(modulename, "out of memory\n");
+ }
+
+ do
+ {
+ const char *keywords[7];
+ const char *values[7];
+
+ keywords[0] = "host";
+ values[0] = PQhost(AH->connection);
+ keywords[1] = "port";
+ values[1] = PQport(AH->connection);
+ keywords[2] = "user";
+ values[2] = newuser;
+ keywords[3] = "password";
+ values[3] = password;
+ keywords[4] = "dbname";
+ values[4] = newdb;
+ keywords[5] = "fallback_application_name";
+ values[5] = progname;
+ keywords[6] = NULL;
+ values[6] = NULL;
+
+ new_pass = false;
+ newConn = PQconnectdbParams(keywords, values, true);
+
+ if (!newConn)
+ exit_horribly(modulename, "failed to reconnect to database\n");
+
+ if (PQstatus(newConn) == CONNECTION_BAD)
+ {
+ if (!PQconnectionNeedsPassword(newConn))
+ exit_horribly(modulename, "could not reconnect to database: %s",
+ PQerrorMessage(newConn));
+ PQfinish(newConn);
+
+ if (password)
+ fprintf(stderr, "Password incorrect\n");
+
+ fprintf(stderr, "Connecting to %s as %s\n",
+ newdb, newuser);
+
+ if (password)
+ free(password);
+
+ if (AH->promptPassword != TRI_NO)
+ password = simple_prompt("Password: ", 100, false);
+ else
+ exit_horribly(modulename, "connection needs password\n");
+
+ if (password == NULL)
+ exit_horribly(modulename, "out of memory\n");
+ new_pass = true;
+ }
+ } while (new_pass);
+
+ /*
+ * We want to remember connection's actual password, whether or not we got
+ * it by prompting. So we don't just store the password variable.
+ */
+ if (PQconnectionUsedPassword(newConn))
+ {
+ if (AH->savedPassword)
+ free(AH->savedPassword);
+ AH->savedPassword = pg_strdup(PQpass(newConn));
+ }
+ if (password)
+ free(password);
+
+ /* check for version mismatch */
+ _check_database_version(AH);
+
+ PQsetNoticeProcessor(newConn, notice_processor, NULL);
+
+ return newConn;
+}
+
+
+/*
+ * Make a database connection with the given parameters. The
+ * connection handle is returned, the parameters are stored in AHX.
+ * An interactive password prompt is automatically issued if required.
+ *
+ * Note: it's not really all that sensible to use a single-entry password
+ * cache if the username keeps changing. In current usage, however, the
+ * username never does change, so one savedPassword is sufficient.
+ */
+void
+ConnectDatabase(Archive *AHX,
+ const char *dbname,
+ const char *pghost,
+ const char *pgport,
+ const char *username,
+ enum trivalue prompt_password)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ char *password;
+ bool new_pass;
+
+ if (AH->connection)
+ exit_horribly(modulename, "already connected to a database\n");
+
+ password = AH->savedPassword ? pg_strdup(AH->savedPassword) : NULL;
+
+ if (prompt_password == TRI_YES && password == NULL)
+ {
+ password = simple_prompt("Password: ", 100, false);
+ if (password == NULL)
+ exit_horribly(modulename, "out of memory\n");
+ }
+ AH->promptPassword = prompt_password;
+
+ /*
+ * Start the connection. Loop until we have a password if requested by
+ * backend.
+ */
+ do
+ {
+ const char *keywords[7];
+ const char *values[7];
+
+ keywords[0] = "host";
+ values[0] = pghost;
+ keywords[1] = "port";
+ values[1] = pgport;
+ keywords[2] = "user";
+ values[2] = username;
+ keywords[3] = "password";
+ values[3] = password;
+ keywords[4] = "dbname";
+ values[4] = dbname;
+ keywords[5] = "fallback_application_name";
+ values[5] = progname;
+ keywords[6] = NULL;
+ values[6] = NULL;
+
+ new_pass = false;
+ AH->connection = PQconnectdbParams(keywords, values, true);
+
+ if (!AH->connection)
+ exit_horribly(modulename, "failed to connect to database\n");
+
+ if (PQstatus(AH->connection) == CONNECTION_BAD &&
+ PQconnectionNeedsPassword(AH->connection) &&
+ password == NULL &&
+ prompt_password != TRI_NO)
+ {
+ PQfinish(AH->connection);
+ password = simple_prompt("Password: ", 100, false);
+ if (password == NULL)
+ exit_horribly(modulename, "out of memory\n");
+ new_pass = true;
+ }
+ } while (new_pass);
+
+ /* check to see that the backend connection was successfully made */
+ if (PQstatus(AH->connection) == CONNECTION_BAD)
+ exit_horribly(modulename, "connection to database \"%s\" failed: %s",
+ PQdb(AH->connection) ? PQdb(AH->connection) : "",
+ PQerrorMessage(AH->connection));
+
+ /*
+ * We want to remember connection's actual password, whether or not we got
+ * it by prompting. So we don't just store the password variable.
+ */
+ if (PQconnectionUsedPassword(AH->connection))
+ {
+ if (AH->savedPassword)
+ free(AH->savedPassword);
+ AH->savedPassword = pg_strdup(PQpass(AH->connection));
+ }
+ if (password)
+ free(password);
+
+ /* check for version mismatch */
+ _check_database_version(AH);
+
+ PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
+}
+
+/*
+ * Close the connection to the database and also cancel off the query if we
+ * have one running.
+ */
+void
+DisconnectDatabase(Archive *AHX)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ PGcancel *cancel;
+ char errbuf[1];
+
+ if (!AH->connection)
+ return;
+
+ if (PQtransactionStatus(AH->connection) == PQTRANS_ACTIVE)
+ {
+ if ((cancel = PQgetCancel(AH->connection)))
+ {
+ PQcancel(cancel, errbuf, sizeof(errbuf));
+ PQfreeCancel(cancel);
+ }
+ }
+
+ PQfinish(AH->connection);
+ AH->connection = NULL;
+}
+
+PGconn *
+GetConnection(Archive *AHX)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+
+ return AH->connection;
+}
+
+static void
+notice_processor(void *arg, const char *message)
+{
+ write_msg(NULL, "%s", message);
+}
+
+/* Like exit_horribly(), but with a complaint about a particular query. */
+static void
+die_on_query_failure(ArchiveHandle *AH, const char *modulename, const char *query)
+{
+ write_msg(modulename, "query failed: %s",
+ PQerrorMessage(AH->connection));
+ exit_horribly(modulename, "query was: %s\n", query);
+}
+
+void
+ExecuteSqlStatement(Archive *AHX, const char *query)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ PGresult *res;
+
+ res = PQexec(AH->connection, query);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ die_on_query_failure(AH, modulename, query);
+ PQclear(res);
+}
+
+PGresult *
+ExecuteSqlQuery(Archive *AHX, const char *query, ExecStatusType status)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) AHX;
+ PGresult *res;
+
+ res = PQexec(AH->connection, query);
+ if (PQresultStatus(res) != status)
+ die_on_query_failure(AH, modulename, query);
+ return res;
+}
+
+/*
+ * Convenience function to send a query.
+ * Monitors result to detect COPY statements
+ */
+static void
+ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc)
+{
+ PGconn *conn = AH->connection;
+ PGresult *res;
+ char errStmt[DB_MAX_ERR_STMT];
+
+#ifdef NOT_USED
+ fprintf(stderr, "Executing: '%s'\n\n", qry);
+#endif
+ res = PQexec(conn, qry);
+
+ switch (PQresultStatus(res))
+ {
+ case PGRES_COMMAND_OK:
+ case PGRES_TUPLES_OK:
+ case PGRES_EMPTY_QUERY:
+ /* A-OK */
+ break;
+ case PGRES_COPY_IN:
+ /* Assume this is an expected result */
+ AH->pgCopyIn = true;
+ break;
+ default:
+ /* trouble */
+ strncpy(errStmt, qry, DB_MAX_ERR_STMT);
+ if (errStmt[DB_MAX_ERR_STMT - 1] != '\0')
+ {
+ errStmt[DB_MAX_ERR_STMT - 4] = '.';
+ errStmt[DB_MAX_ERR_STMT - 3] = '.';
+ errStmt[DB_MAX_ERR_STMT - 2] = '.';
+ errStmt[DB_MAX_ERR_STMT - 1] = '\0';
+ }
+ warn_or_exit_horribly(AH, modulename, "%s: %s Command was: %s\n",
+ desc, PQerrorMessage(conn), errStmt);
+ break;
+ }
+
+ PQclear(res);
+}
+
+
+/*
+ * Process non-COPY table data (that is, INSERT commands).
+ *
+ * The commands have been run together as one long string for compressibility,
+ * and we are receiving them in bufferloads with arbitrary boundaries, so we
+ * have to locate command boundaries and save partial commands across calls.
+ * All state must be kept in AH->sqlparse, not in local variables of this
+ * routine. We assume that AH->sqlparse was filled with zeroes when created.
+ *
+ * We have to lex the data to the extent of identifying literals and quoted
+ * identifiers, so that we can recognize statement-terminating semicolons.
+ * We assume that INSERT data will not contain SQL comments, E'' literals,
+ * or dollar-quoted strings, so this is much simpler than a full SQL lexer.
+ *
+ * Note: when restoring from a pre-9.0 dump file, this code is also used to
+ * process BLOB COMMENTS data, which has the same problem of containing
+ * multiple SQL commands that might be split across bufferloads. Fortunately,
+ * that data won't contain anything complicated to lex either.
+ */
+static void
+ExecuteSimpleCommands(ArchiveHandle *AH, const char *buf, size_t bufLen)
+{
+ const char *qry = buf;
+ const char *eos = buf + bufLen;
+
+ /* initialize command buffer if first time through */
+ if (AH->sqlparse.curCmd == NULL)
+ AH->sqlparse.curCmd = createPQExpBuffer();
+
+ for (; qry < eos; qry++)
+ {
+ char ch = *qry;
+
+ /* For neatness, we skip any newlines between commands */
+ if (!(ch == '\n' && AH->sqlparse.curCmd->len == 0))
+ appendPQExpBufferChar(AH->sqlparse.curCmd, ch);
+
+ switch (AH->sqlparse.state)
+ {
+ case SQL_SCAN: /* Default state == 0, set in _allocAH */
+ if (ch == ';')
+ {
+ /*
+ * We've found the end of a statement. Send it and reset
+ * the buffer.
+ */
+ ExecuteSqlCommand(AH, AH->sqlparse.curCmd->data,
+ "could not execute query");
+ resetPQExpBuffer(AH->sqlparse.curCmd);
+ }
+ else if (ch == '\'')
+ {
+ AH->sqlparse.state = SQL_IN_SINGLE_QUOTE;
+ AH->sqlparse.backSlash = false;
+ }
+ else if (ch == '"')
+ {
+ AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE;
+ }
+ break;
+
+ case SQL_IN_SINGLE_QUOTE:
+ /* We needn't handle '' specially */
+ if (ch == '\'' && !AH->sqlparse.backSlash)
+ AH->sqlparse.state = SQL_SCAN;
+ else if (ch == '\\' && !AH->public.std_strings)
+ AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+ else
+ AH->sqlparse.backSlash = false;
+ break;
+
+ case SQL_IN_DOUBLE_QUOTE:
+ /* We needn't handle "" specially */
+ if (ch == '"')
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+ }
+ }
+}
+
+
+/*
+ * Implement ahwrite() for direct-to-DB restore
+ */
+int
+ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen)
+{
+ if (AH->outputKind == OUTPUT_COPYDATA)
+ {
+ /*
+ * COPY data.
+ *
+ * We drop the data on the floor if libpq has failed to enter COPY
+ * mode; this allows us to behave reasonably when trying to continue
+ * after an error in a COPY command.
+ */
+ if (AH->pgCopyIn &&
+ PQputCopyData(AH->connection, buf, bufLen) <= 0)
+ exit_horribly(modulename, "error returned by PQputCopyData: %s",
+ PQerrorMessage(AH->connection));
+ }
+ else if (AH->outputKind == OUTPUT_OTHERDATA)
+ {
+ /*
+ * Table data expressed as INSERT commands; or, in old dump files,
+ * BLOB COMMENTS data (which is expressed as COMMENT ON commands).
+ */
+ ExecuteSimpleCommands(AH, buf, bufLen);
+ }
+ else
+ {
+ /*
+ * General SQL commands; we assume that commands will not be split
+ * across calls.
+ *
+ * In most cases the data passed to us will be a null-terminated
+ * string, but if it's not, we have to add a trailing null.
+ */
+ if (buf[bufLen] == '\0')
+ ExecuteSqlCommand(AH, buf, "could not execute query");
+ else
+ {
+ char *str = (char *) pg_malloc(bufLen + 1);
+
+ memcpy(str, buf, bufLen);
+ str[bufLen] = '\0';
+ ExecuteSqlCommand(AH, str, "could not execute query");
+ free(str);
+ }
+ }
+
+ return bufLen;
+}
+
+/*
+ * Terminate a COPY operation during direct-to-DB restore
+ */
+void
+EndDBCopyMode(ArchiveHandle *AH, TocEntry *te)
+{
+ if (AH->pgCopyIn)
+ {
+ PGresult *res;
+
+ if (PQputCopyEnd(AH->connection, NULL) <= 0)
+ exit_horribly(modulename, "error returned by PQputCopyEnd: %s",
+ PQerrorMessage(AH->connection));
+
+ /* Check command status and return to normal libpq state */
+ res = PQgetResult(AH->connection);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ warn_or_exit_horribly(AH, modulename, "COPY failed for table \"%s\": %s",
+ te->tag, PQerrorMessage(AH->connection));
+ PQclear(res);
+
+ AH->pgCopyIn = false;
+ }
+}
+
+void
+StartTransaction(ArchiveHandle *AH)
+{
+ ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction");
+}
+
+void
+CommitTransaction(ArchiveHandle *AH)
+{
+ ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction");
+}
+
+void
+DropBlobIfExists(ArchiveHandle *AH, Oid oid)
+{
+ /*
+ * If we are not restoring to a direct database connection, we have to
+ * guess about how to detect whether the blob exists. Assume new-style.
+ */
+ if (AH->connection == NULL ||
+ PQserverVersion(AH->connection) >= 90000)
+ {
+ ahprintf(AH,
+ "SELECT pg_catalog.lo_unlink(oid) "
+ "FROM pg_catalog.pg_largeobject_metadata "
+ "WHERE oid = '%u';\n",
+ oid);
+ }
+ else
+ {
+ /* Restoring to pre-9.0 server, so do it the old way */
+ ahprintf(AH,
+ "SELECT CASE WHEN EXISTS("
+ "SELECT 1 FROM pg_catalog.pg_largeobject WHERE loid = '%u'"
+ ") THEN pg_catalog.lo_unlink('%u') END;\n",
+ oid, oid);
+ }
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_db.h
@@ -0,0 +1,24 @@
+/*
+ * Definitions for pg_backup_db.c
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_db.h
+ */
+
+#ifndef PG_BACKUP_DB_H
+#define PG_BACKUP_DB_H
+
+#include "pg_backup_archiver.h"
+
+extern int ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen);
+
+extern void ExecuteSqlStatement(Archive *AHX, const char *query);
+extern PGresult *ExecuteSqlQuery(Archive *AHX, const char *query,
+ ExecStatusType status);
+
+extern void EndDBCopyMode(ArchiveHandle *AH, struct _tocEntry * te);
+
+extern void StartTransaction(ArchiveHandle *AH);
+extern void CommitTransaction(ArchiveHandle *AH);
+
+#endif
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_directory.c
@@ -0,0 +1,877 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_directory.c
+ *
+ * A directory format dump is a directory, which contains a "toc.dat" file
+ * for the TOC, and a separate file for each data entry, named "<oid>.dat".
+ * Large objects (BLOBs) are stored in separate files named "blob_<uid>.dat",
+ * and there's a plain-text TOC file for them called "blobs.toc". If
+ * compression is used, each data file is individually compressed and the
+ * ".gz" suffix is added to the filenames. The TOC files are never
+ * compressed by pg_dump, however they are accepted with the .gz suffix too,
+ * in case the user has manually compressed them with 'gzip'.
+ *
+ * NOTE: This format is identical to the files written in the tar file in
+ * the 'tar' format, except that we don't write the restore.sql file (TODO),
+ * and the tar format doesn't support compression. Please keep the formats in
+ * sync.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 2000, Philip Warner
+ *
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_directory.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "compress_io.h"
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+typedef struct
+{
+ /*
+ * Our archive location. This is basically what the user specified as his
+ * backup file but of course here it is a directory.
+ */
+ char *directory;
+
+ cfp *dataFH; /* currently open data file */
+
+ cfp *blobsTocFH; /* file handle for blobs.toc */
+ ParallelState *pstate; /* for parallel backup / restore */
+} lclContext;
+
+typedef struct
+{
+ char *filename; /* filename excluding the directory (basename) */
+} lclTocEntry;
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("directory archiver");
+
+/* prototypes for private functions */
+static void _ArchiveEntry(ArchiveHandle *AH, TocEntry *te);
+static void _StartData(ArchiveHandle *AH, TocEntry *te);
+static void _EndData(ArchiveHandle *AH, TocEntry *te);
+static void _WriteData(ArchiveHandle *AH, const void *data, size_t dLen);
+static int _WriteByte(ArchiveHandle *AH, const int i);
+static int _ReadByte(ArchiveHandle *);
+static void _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len);
+static void _ReadBuf(ArchiveHandle *AH, void *buf, size_t len);
+static void _CloseArchive(ArchiveHandle *AH);
+static void _ReopenArchive(ArchiveHandle *AH);
+static void _PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt);
+
+static void _WriteExtraToc(ArchiveHandle *AH, TocEntry *te);
+static void _ReadExtraToc(ArchiveHandle *AH, TocEntry *te);
+static void _PrintExtraToc(ArchiveHandle *AH, TocEntry *te);
+
+static void _StartBlobs(ArchiveHandle *AH, TocEntry *te);
+static void _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlobs(ArchiveHandle *AH, TocEntry *te);
+static void _LoadBlobs(ArchiveHandle *AH, RestoreOptions *ropt);
+
+static void _Clone(ArchiveHandle *AH);
+static void _DeClone(ArchiveHandle *AH);
+
+static char *_MasterStartParallelItem(ArchiveHandle *AH, TocEntry *te, T_Action act);
+static int _MasterEndParallelItem(ArchiveHandle *AH, TocEntry *te,
+ const char *str, T_Action act);
+static char *_WorkerJobRestoreDirectory(ArchiveHandle *AH, TocEntry *te);
+static char *_WorkerJobDumpDirectory(ArchiveHandle *AH, TocEntry *te);
+
+static void setFilePath(ArchiveHandle *AH, char *buf,
+ const char *relativeFilename);
+
+/*
+ * Init routine required by ALL formats. This is a global routine
+ * and should be declared in pg_backup_archiver.h
+ *
+ * Its task is to create any extra archive context (using AH->formatData),
+ * and to initialize the supported function pointers.
+ *
+ * It should also prepare whatever its input source is for reading/writing,
+ * and in the case of a read mode connection, it should load the Header & TOC.
+ */
+void
+InitArchiveFmt_Directory(ArchiveHandle *AH)
+{
+ lclContext *ctx;
+
+ /* Assuming static functions, this can be copied for each format. */
+ AH->ArchiveEntryPtr = _ArchiveEntry;
+ AH->StartDataPtr = _StartData;
+ AH->WriteDataPtr = _WriteData;
+ AH->EndDataPtr = _EndData;
+ AH->WriteBytePtr = _WriteByte;
+ AH->ReadBytePtr = _ReadByte;
+ AH->WriteBufPtr = _WriteBuf;
+ AH->ReadBufPtr = _ReadBuf;
+ AH->ClosePtr = _CloseArchive;
+ AH->ReopenPtr = _ReopenArchive;
+ AH->PrintTocDataPtr = _PrintTocData;
+ AH->ReadExtraTocPtr = _ReadExtraToc;
+ AH->WriteExtraTocPtr = _WriteExtraToc;
+ AH->PrintExtraTocPtr = _PrintExtraToc;
+
+ AH->StartBlobsPtr = _StartBlobs;
+ AH->StartBlobPtr = _StartBlob;
+ AH->EndBlobPtr = _EndBlob;
+ AH->EndBlobsPtr = _EndBlobs;
+
+ AH->ClonePtr = _Clone;
+ AH->DeClonePtr = _DeClone;
+
+ AH->WorkerJobRestorePtr = _WorkerJobRestoreDirectory;
+ AH->WorkerJobDumpPtr = _WorkerJobDumpDirectory;
+
+ AH->MasterStartParallelItemPtr = _MasterStartParallelItem;
+ AH->MasterEndParallelItemPtr = _MasterEndParallelItem;
+
+ /* Set up our private context */
+ ctx = (lclContext *) pg_malloc0(sizeof(lclContext));
+ AH->formatData = (void *) ctx;
+
+ ctx->dataFH = NULL;
+ ctx->blobsTocFH = NULL;
+
+ /* Initialize LO buffering */
+ AH->lo_buf_size = LOBBUFSIZE;
+ AH->lo_buf = (void *) pg_malloc(LOBBUFSIZE);
+
+ /*
+ * Now open the TOC file
+ */
+
+ if (!AH->fSpec || strcmp(AH->fSpec, "") == 0)
+ exit_horribly(modulename, "no output directory specified\n");
+
+ ctx->directory = AH->fSpec;
+
+ if (AH->mode == archModeWrite)
+ {
+ struct stat st;
+ bool is_empty = false;
+
+ /* we accept an empty existing directory */
+ if (stat(ctx->directory, &st) == 0 && S_ISDIR(st.st_mode))
+ {
+ DIR *dir = opendir(ctx->directory);
+
+ if (dir)
+ {
+ struct dirent *d;
+
+ is_empty = true;
+ while (errno = 0, (d = readdir(dir)))
+ {
+ if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0)
+ {
+ is_empty = false;
+ break;
+ }
+ }
+
+ if (errno)
+ exit_horribly(modulename, "could not read directory \"%s\": %s\n",
+ ctx->directory, strerror(errno));
+
+ if (closedir(dir))
+ exit_horribly(modulename, "could not close directory \"%s\": %s\n",
+ ctx->directory, strerror(errno));
+ }
+ }
+
+ if (!is_empty && mkdir(ctx->directory, 0700) < 0)
+ exit_horribly(modulename, "could not create directory \"%s\": %s\n",
+ ctx->directory, strerror(errno));
+ }
+ else
+ { /* Read Mode */
+ char fname[MAXPGPATH];
+ cfp *tocFH;
+
+ setFilePath(AH, fname, "toc.dat");
+
+ tocFH = cfopen_read(fname, PG_BINARY_R);
+ if (tocFH == NULL)
+ exit_horribly(modulename,
+ "could not open input file \"%s\": %s\n",
+ fname, strerror(errno));
+
+ ctx->dataFH = tocFH;
+
+ /*
+ * The TOC of a directory format dump shares the format code of the
+ * tar format.
+ */
+ AH->format = archTar;
+ ReadHead(AH);
+ AH->format = archDirectory;
+ ReadToc(AH);
+
+ /* Nothing else in the file, so close it again... */
+ if (cfclose(tocFH) != 0)
+ exit_horribly(modulename, "could not close TOC file: %s\n",
+ strerror(errno));
+ ctx->dataFH = NULL;
+ }
+}
+
+/*
+ * Called by the Archiver when the dumper creates a new TOC entry.
+ *
+ * We determine the filename for this entry.
+*/
+static void
+_ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx;
+ char fn[MAXPGPATH];
+
+ tctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ if (te->dataDumper)
+ {
+ snprintf(fn, MAXPGPATH, "%d.dat", te->dumpId);
+ tctx->filename = pg_strdup(fn);
+ }
+ else if (strcmp(te->desc, "BLOBS") == 0)
+ tctx->filename = pg_strdup("blobs.toc");
+ else
+ tctx->filename = NULL;
+
+ te->formatData = (void *) tctx;
+}
+
+/*
+ * Called by the Archiver to save any extra format-related TOC entry
+ * data.
+ *
+ * Use the Archiver routines to write data - they are non-endian, and
+ * maintain other important file information.
+ */
+static void
+_WriteExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ /*
+ * A dumpable object has set tctx->filename, any other object has not.
+ * (see _ArchiveEntry).
+ */
+ if (tctx->filename)
+ WriteStr(AH, tctx->filename);
+ else
+ WriteStr(AH, "");
+}
+
+/*
+ * Called by the Archiver to read any extra format-related TOC data.
+ *
+ * Needs to match the order defined in _WriteExtraToc, and should also
+ * use the Archiver input routines.
+ */
+static void
+_ReadExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ if (tctx == NULL)
+ {
+ tctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ te->formatData = (void *) tctx;
+ }
+
+ tctx->filename = ReadStr(AH);
+ if (strlen(tctx->filename) == 0)
+ {
+ free(tctx->filename);
+ tctx->filename = NULL;
+ }
+}
+
+/*
+ * Called by the Archiver when restoring an archive to output a comment
+ * that includes useful information about the TOC entry.
+ */
+static void
+_PrintExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ if (AH->public.verbose && tctx->filename)
+ ahprintf(AH, "-- File: %s\n", tctx->filename);
+}
+
+/*
+ * Called by the archiver when saving TABLE DATA (not schema). This routine
+ * should save whatever format-specific information is needed to read
+ * the archive back.
+ *
+ * It is called just prior to the dumper's 'DataDumper' routine being called.
+ *
+ * We create the data file for writing.
+ */
+static void
+_StartData(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char fname[MAXPGPATH];
+
+ setFilePath(AH, fname, tctx->filename);
+
+ ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
+ if (ctx->dataFH == NULL)
+ exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ fname, strerror(errno));
+}
+
+/*
+ * Called by archiver when dumper calls WriteData. This routine is
+ * called for both BLOB and TABLE data; it is the responsibility of
+ * the format to manage each kind of data using StartBlob/StartData.
+ *
+ * It should only be called from within a DataDumper routine.
+ *
+ * We write the data to the open data file.
+ */
+static void
+_WriteData(ArchiveHandle *AH, const void *data, size_t dLen)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ /* Are we aborting? */
+ checkAborting(AH);
+
+ if (dLen > 0 && cfwrite(data, dLen, ctx->dataFH) != dLen)
+ WRITE_ERROR_EXIT;
+
+ return;
+}
+
+/*
+ * Called by the archiver when a dumper's 'DataDumper' routine has
+ * finished.
+ *
+ * We close the data file.
+ */
+static void
+_EndData(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ /* Close the file */
+ cfclose(ctx->dataFH);
+
+ ctx->dataFH = NULL;
+}
+
+/*
+ * Print data for a given file (can be a BLOB as well)
+ */
+static void
+_PrintFileData(ArchiveHandle *AH, char *filename, RestoreOptions *ropt)
+{
+ size_t cnt;
+ char *buf;
+ size_t buflen;
+ cfp *cfp;
+
+ if (!filename)
+ return;
+
+ cfp = cfopen_read(filename, PG_BINARY_R);
+
+ if (!cfp)
+ exit_horribly(modulename, "could not open input file \"%s\": %s\n",
+ filename, strerror(errno));
+
+ buf = pg_malloc(ZLIB_OUT_SIZE);
+ buflen = ZLIB_OUT_SIZE;
+
+ while ((cnt = cfread(buf, buflen, cfp)))
+ ahwrite(buf, 1, cnt, AH);
+
+ free(buf);
+ if (cfclose(cfp) !=0)
+ exit_horribly(modulename, "could not close data file: %s\n",
+ strerror(errno));
+}
+
+/*
+ * Print data for a given TOC entry
+*/
+static void
+_PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ if (!tctx->filename)
+ return;
+
+ if (strcmp(te->desc, "BLOBS") == 0)
+ _LoadBlobs(AH, ropt);
+ else
+ {
+ char fname[MAXPGPATH];
+
+ setFilePath(AH, fname, tctx->filename);
+ _PrintFileData(AH, fname, ropt);
+ }
+}
+
+static void
+_LoadBlobs(ArchiveHandle *AH, RestoreOptions *ropt)
+{
+ Oid oid;
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char fname[MAXPGPATH];
+ char line[MAXPGPATH];
+
+ StartRestoreBlobs(AH);
+
+ setFilePath(AH, fname, "blobs.toc");
+
+ ctx->blobsTocFH = cfopen_read(fname, PG_BINARY_R);
+
+ if (ctx->blobsTocFH == NULL)
+ exit_horribly(modulename, "could not open large object TOC file \"%s\" for input: %s\n",
+ fname, strerror(errno));
+
+ /* Read the blobs TOC file line-by-line, and process each blob */
+ while ((cfgets(ctx->blobsTocFH, line, MAXPGPATH)) != NULL)
+ {
+ char fname[MAXPGPATH];
+ char path[MAXPGPATH];
+
+ /* Can't overflow because line and fname are the same length. */
+ if (sscanf(line, "%u %s\n", &oid, fname) != 2)
+ exit_horribly(modulename, "invalid line in large object TOC file \"%s\": \"%s\"\n",
+ fname, line);
+
+ StartRestoreBlob(AH, oid, ropt->dropSchema);
+ snprintf(path, MAXPGPATH, "%s/%s", ctx->directory, fname);
+ _PrintFileData(AH, path, ropt);
+ EndRestoreBlob(AH, oid);
+ }
+ if (!cfeof(ctx->blobsTocFH))
+ exit_horribly(modulename, "error reading large object TOC file \"%s\"\n",
+ fname);
+
+ if (cfclose(ctx->blobsTocFH) != 0)
+ exit_horribly(modulename, "could not close large object TOC file \"%s\": %s\n",
+ fname, strerror(errno));
+
+ ctx->blobsTocFH = NULL;
+
+ EndRestoreBlobs(AH);
+}
+
+
+/*
+ * Write a byte of data to the archive.
+ * Called by the archiver to do integer & byte output to the archive.
+ * These routines are only used to read & write the headers & TOC.
+ */
+static int
+_WriteByte(ArchiveHandle *AH, const int i)
+{
+ unsigned char c = (unsigned char) i;
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (cfwrite(&c, 1, ctx->dataFH) != 1)
+ WRITE_ERROR_EXIT;
+
+ return 1;
+}
+
+/*
+ * Read a byte of data from the archive.
+ * Called by the archiver to read bytes & integers from the archive.
+ * These routines are only used to read & write headers & TOC.
+ * EOF should be treated as a fatal error.
+ */
+static int
+_ReadByte(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ return cfgetc(ctx->dataFH);
+}
+
+/*
+ * Write a buffer of data to the archive.
+ * Called by the archiver to write a block of bytes to the TOC or a data file.
+ */
+static void
+_WriteBuf(ArchiveHandle *AH, const void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ /* Are we aborting? */
+ checkAborting(AH);
+
+ if (cfwrite(buf, len, ctx->dataFH) != len)
+ WRITE_ERROR_EXIT;
+
+ return;
+}
+
+/*
+ * Read a block of bytes from the archive.
+ *
+ * Called by the archiver to read a block of bytes from the archive
+ */
+static void
+_ReadBuf(ArchiveHandle *AH, void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ /*
+ * If there was an I/O error, we already exited in cfread(), so here we
+ * exit on short reads.
+ */
+ if (cfread(buf, len, ctx->dataFH) != len)
+ exit_horribly(modulename,
+ "could not read from input file: end of file\n");
+
+ return;
+}
+
+/*
+ * Close the archive.
+ *
+ * When writing the archive, this is the routine that actually starts
+ * the process of saving it to files. No data should be written prior
+ * to this point, since the user could sort the TOC after creating it.
+ *
+ * If an archive is to be written, this routine must call:
+ * WriteHead to save the archive header
+ * WriteToc to save the TOC entries
+ * WriteDataChunks to save all DATA & BLOBs.
+ */
+static void
+_CloseArchive(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (AH->mode == archModeWrite)
+ {
+ cfp *tocFH;
+ char fname[MAXPGPATH];
+
+ setFilePath(AH, fname, "toc.dat");
+
+ /* this will actually fork the processes for a parallel backup */
+ ctx->pstate = ParallelBackupStart(AH, NULL);
+
+ /* The TOC is always created uncompressed */
+ tocFH = cfopen_write(fname, PG_BINARY_W, 0);
+ if (tocFH == NULL)
+ exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ fname, strerror(errno));
+ ctx->dataFH = tocFH;
+
+ /*
+ * Write 'tar' in the format field of the toc.dat file. The directory
+ * is compatible with 'tar', so there's no point having a different
+ * format code for it.
+ */
+ AH->format = archTar;
+ WriteHead(AH);
+ AH->format = archDirectory;
+ WriteToc(AH);
+ if (cfclose(tocFH) != 0)
+ exit_horribly(modulename, "could not close TOC file: %s\n",
+ strerror(errno));
+ WriteDataChunks(AH, ctx->pstate);
+
+ ParallelBackupEnd(AH, ctx->pstate);
+ }
+ AH->FH = NULL;
+}
+
+/*
+ * Reopen the archive's file handle.
+ */
+static void
+_ReopenArchive(ArchiveHandle *AH)
+{
+ /*
+ * Our TOC is in memory, our data files are opened by each child anyway as
+ * they are separate. We support reopening the archive by just doing
+ * nothing.
+ */
+}
+
+/*
+ * BLOB support
+ */
+
+/*
+ * Called by the archiver when starting to save all BLOB DATA (not schema).
+ * It is called just prior to the dumper's DataDumper routine.
+ *
+ * We open the large object TOC file here, so that we can append a line to
+ * it for each blob.
+ */
+static void
+_StartBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char fname[MAXPGPATH];
+
+ setFilePath(AH, fname, "blobs.toc");
+
+ /* The blob TOC file is never compressed */
+ ctx->blobsTocFH = cfopen_write(fname, "ab", 0);
+ if (ctx->blobsTocFH == NULL)
+ exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ fname, strerror(errno));
+}
+
+/*
+ * Called by the archiver when we're about to start dumping a blob.
+ *
+ * We create a file to write the blob to.
+ */
+static void
+_StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char fname[MAXPGPATH];
+
+ snprintf(fname, MAXPGPATH, "%s/blob_%u.dat", ctx->directory, oid);
+
+ ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
+
+ if (ctx->dataFH == NULL)
+ exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ fname, strerror(errno));
+}
+
+/*
+ * Called by the archiver when the dumper is finished writing a blob.
+ *
+ * We close the blob file and write an entry to the blob TOC file for it.
+ */
+static void
+_EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char buf[50];
+ int len;
+
+ /* Close the BLOB data file itself */
+ cfclose(ctx->dataFH);
+ ctx->dataFH = NULL;
+
+ /* register the blob in blobs.toc */
+ len = snprintf(buf, sizeof(buf), "%u blob_%u.dat\n", oid, oid);
+ if (cfwrite(buf, len, ctx->blobsTocFH) != len)
+ exit_horribly(modulename, "could not write to blobs TOC file\n");
+}
+
+/*
+ * Called by the archiver when finishing saving all BLOB DATA.
+ *
+ * We close the blobs TOC file.
+ */
+static void
+_EndBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ cfclose(ctx->blobsTocFH);
+ ctx->blobsTocFH = NULL;
+}
+
+/*
+ * Gets a relative file name and prepends the output directory, writing the
+ * result to buf. The caller needs to make sure that buf is MAXPGPATH bytes
+ * big. Can't use a static char[MAXPGPATH] inside the function because we run
+ * multithreaded on Windows.
+ */
+static void
+setFilePath(ArchiveHandle *AH, char *buf, const char *relativeFilename)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char *dname;
+
+ dname = ctx->directory;
+
+ if (strlen(dname) + 1 + strlen(relativeFilename) + 1 > MAXPGPATH)
+ exit_horribly(modulename, "file name too long: \"%s\"\n", dname);
+
+ strcpy(buf, dname);
+ strcat(buf, "/");
+ strcat(buf, relativeFilename);
+}
+
+/*
+ * Clone format-specific fields during parallel restoration.
+ */
+static void
+_Clone(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ AH->formatData = (lclContext *) pg_malloc(sizeof(lclContext));
+ memcpy(AH->formatData, ctx, sizeof(lclContext));
+ ctx = (lclContext *) AH->formatData;
+
+ /*
+ * Note: we do not make a local lo_buf because we expect at most one BLOBS
+ * entry per archive, so no parallelism is possible. Likewise,
+ * TOC-entry-local state isn't an issue because any one TOC entry is
+ * touched by just one worker child.
+ */
+
+ /*
+ * We also don't copy the ParallelState pointer (pstate), only the master
+ * process ever writes to it.
+ */
+}
+
+static void
+_DeClone(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ free(ctx);
+}
+
+/*
+ * This function is executed in the parent process. Depending on the desired
+ * action (dump or restore) it creates a string that is understood by the
+ * _WorkerJobDump /_WorkerJobRestore functions of the dump format.
+ */
+static char *
+_MasterStartParallelItem(ArchiveHandle *AH, TocEntry *te, T_Action act)
+{
+ /*
+ * A static char is okay here, even on Windows because we call this
+ * function only from one process (the master).
+ */
+ static char buf[64];
+
+ if (act == ACT_DUMP)
+ snprintf(buf, sizeof(buf), "DUMP %d", te->dumpId);
+ else if (act == ACT_RESTORE)
+ snprintf(buf, sizeof(buf), "RESTORE %d", te->dumpId);
+
+ return buf;
+}
+
+/*
+ * This function is executed in the child of a parallel backup for the
+ * directory archive and dumps the actual data.
+ *
+ * We are currently returning only the DumpId so theoretically we could
+ * make this function returning an int (or a DumpId). However, to
+ * facilitate further enhancements and because sooner or later we need to
+ * convert this to a string and send it via a message anyway, we stick with
+ * char *. It is parsed on the other side by the _EndMasterParallel()
+ * function of the respective dump format.
+ */
+static char *
+_WorkerJobDumpDirectory(ArchiveHandle *AH, TocEntry *te)
+{
+ /*
+ * short fixed-size string + some ID so far, this needs to be malloc'ed
+ * instead of static because we work with threads on windows
+ */
+ const int buflen = 64;
+ char *buf = (char *) pg_malloc(buflen);
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ /* This should never happen */
+ if (!tctx)
+ exit_horribly(modulename, "error during backup\n");
+
+ /*
+ * This function returns void. We either fail and die horribly or
+ * succeed... A failure will be detected by the parent when the child dies
+ * unexpectedly.
+ */
+ WriteDataChunksForTocEntry(AH, te);
+
+ snprintf(buf, buflen, "OK DUMP %d", te->dumpId);
+
+ return buf;
+}
+
+/*
+ * This function is executed in the child of a parallel backup for the
+ * directory archive and dumps the actual data.
+ */
+static char *
+_WorkerJobRestoreDirectory(ArchiveHandle *AH, TocEntry *te)
+{
+ /*
+ * short fixed-size string + some ID so far, this needs to be malloc'ed
+ * instead of static because we work with threads on windows
+ */
+ const int buflen = 64;
+ char *buf = (char *) pg_malloc(buflen);
+ ParallelArgs pargs;
+ int status;
+
+ pargs.AH = AH;
+ pargs.te = te;
+
+ status = parallel_restore(&pargs);
+
+ snprintf(buf, buflen, "OK RESTORE %d %d %d", te->dumpId, status,
+ status == WORKER_IGNORED_ERRORS ? AH->public.n_errors : 0);
+
+ return buf;
+}
+
+/*
+ * This function is executed in the parent process. It analyzes the response of
+ * the _WorkerJobDumpDirectory/_WorkerJobRestoreDirectory functions of the
+ * respective dump format.
+ */
+static int
+_MasterEndParallelItem(ArchiveHandle *AH, TocEntry *te, const char *str, T_Action act)
+{
+ DumpId dumpId;
+ int nBytes,
+ n_errors;
+ int status = 0;
+
+ if (act == ACT_DUMP)
+ {
+ sscanf(str, "%u%n", &dumpId, &nBytes);
+
+ Assert(dumpId == te->dumpId);
+ Assert(nBytes == strlen(str));
+ }
+ else if (act == ACT_RESTORE)
+ {
+ sscanf(str, "%u %u %u%n", &dumpId, &status, &n_errors, &nBytes);
+
+ Assert(dumpId == te->dumpId);
+ Assert(nBytes == strlen(str));
+
+ AH->public.n_errors += n_errors;
+ }
+
+ return status;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_null.c
@@ -0,0 +1,233 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_null.c
+ *
+ * Implementation of an archive that is never saved; it is used by
+ * pg_dump to output a plain text SQL script instead of saving
+ * a real archive.
+ *
+ * See the headers to pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_null.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "pg_backup_archiver.h"
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+#include <unistd.h> /* for dup */
+
+#include "libpq/libpq-fs.h"
+
+static void _WriteData(ArchiveHandle *AH, const void *data, size_t dLen);
+static void _WriteBlobData(ArchiveHandle *AH, const void *data, size_t dLen);
+static void _EndData(ArchiveHandle *AH, TocEntry *te);
+static int _WriteByte(ArchiveHandle *AH, const int i);
+static void _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len);
+static void _CloseArchive(ArchiveHandle *AH);
+static void _PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt);
+static void _StartBlobs(ArchiveHandle *AH, TocEntry *te);
+static void _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlobs(ArchiveHandle *AH, TocEntry *te);
+
+
+/*
+ * Initializer
+ */
+void
+InitArchiveFmt_Null(ArchiveHandle *AH)
+{
+ /* Assuming static functions, this can be copied for each format. */
+ AH->WriteDataPtr = _WriteData;
+ AH->EndDataPtr = _EndData;
+ AH->WriteBytePtr = _WriteByte;
+ AH->WriteBufPtr = _WriteBuf;
+ AH->ClosePtr = _CloseArchive;
+ AH->ReopenPtr = NULL;
+ AH->PrintTocDataPtr = _PrintTocData;
+
+ AH->StartBlobsPtr = _StartBlobs;
+ AH->StartBlobPtr = _StartBlob;
+ AH->EndBlobPtr = _EndBlob;
+ AH->EndBlobsPtr = _EndBlobs;
+ AH->ClonePtr = NULL;
+ AH->DeClonePtr = NULL;
+
+ /* Initialize LO buffering */
+ AH->lo_buf_size = LOBBUFSIZE;
+ AH->lo_buf = (void *) pg_malloc(LOBBUFSIZE);
+
+ /*
+ * Now prevent reading...
+ */
+ if (AH->mode == archModeRead)
+ exit_horribly(NULL, "this format cannot be read\n");
+}
+
+/*
+ * - Start a new TOC entry
+ */
+
+/*
+ * Called by dumper via archiver from within a data dump routine
+ */
+static void
+_WriteData(ArchiveHandle *AH, const void *data, size_t dLen)
+{
+ /* Just send it to output, ahwrite() already errors on failure */
+ ahwrite(data, 1, dLen, AH);
+ return;
+}
+
+/*
+ * Called by dumper via archiver from within a data dump routine
+ * We substitute this for _WriteData while emitting a BLOB
+ */
+static void
+_WriteBlobData(ArchiveHandle *AH, const void *data, size_t dLen)
+{
+ if (dLen > 0)
+ {
+ PQExpBuffer buf = createPQExpBuffer();
+
+ appendByteaLiteralAHX(buf,
+ (const unsigned char *) data,
+ dLen,
+ AH);
+
+ ahprintf(AH, "SELECT pg_catalog.lowrite(0, %s);\n", buf->data);
+
+ destroyPQExpBuffer(buf);
+ }
+ return;
+}
+
+static void
+_EndData(ArchiveHandle *AH, TocEntry *te)
+{
+ ahprintf(AH, "\n\n");
+}
+
+/*
+ * Called by the archiver when starting to save all BLOB DATA (not schema).
+ * This routine should save whatever format-specific information is needed
+ * to read the BLOBs back into memory.
+ *
+ * It is called just prior to the dumper's DataDumper routine.
+ *
+ * Optional, but strongly recommended.
+ */
+static void
+_StartBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ ahprintf(AH, "BEGIN;\n\n");
+}
+
+/*
+ * Called by the archiver when the dumper calls StartBlob.
+ *
+ * Mandatory.
+ *
+ * Must save the passed OID for retrieval at restore-time.
+ */
+static void
+_StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ bool old_blob_style = (AH->version < K_VERS_1_12);
+
+ if (oid == 0)
+ exit_horribly(NULL, "invalid OID for large object\n");
+
+ /* With an old archive we must do drop and create logic here */
+ if (old_blob_style && AH->ropt->dropSchema)
+ DropBlobIfExists(AH, oid);
+
+ if (old_blob_style)
+ ahprintf(AH, "SELECT pg_catalog.lo_open(pg_catalog.lo_create('%u'), %d);\n",
+ oid, INV_WRITE);
+ else
+ ahprintf(AH, "SELECT pg_catalog.lo_open('%u', %d);\n",
+ oid, INV_WRITE);
+
+ AH->WriteDataPtr = _WriteBlobData;
+}
+
+/*
+ * Called by the archiver when the dumper calls EndBlob.
+ *
+ * Optional.
+ */
+static void
+_EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ AH->WriteDataPtr = _WriteData;
+
+ ahprintf(AH, "SELECT pg_catalog.lo_close(0);\n\n");
+}
+
+/*
+ * Called by the archiver when finishing saving all BLOB DATA.
+ *
+ * Optional.
+ */
+static void
+_EndBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ ahprintf(AH, "COMMIT;\n\n");
+}
+
+/*------
+ * Called as part of a RestoreArchive call; for the NULL archive, this
+ * just sends the data for a given TOC entry to the output.
+ *------
+ */
+static void
+_PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt)
+{
+ if (te->dataDumper)
+ {
+ AH->currToc = te;
+
+ if (strcmp(te->desc, "BLOBS") == 0)
+ _StartBlobs(AH, te);
+
+ (*te->dataDumper) ((Archive *) AH, te->dataDumperArg);
+
+ if (strcmp(te->desc, "BLOBS") == 0)
+ _EndBlobs(AH, te);
+
+ AH->currToc = NULL;
+ }
+}
+
+static int
+_WriteByte(ArchiveHandle *AH, const int i)
+{
+ /* Don't do anything */
+ return 0;
+}
+
+static void
+_WriteBuf(ArchiveHandle *AH, const void *buf, size_t len)
+{
+ /* Don't do anything */
+ return;
+}
+
+static void
+_CloseArchive(ArchiveHandle *AH)
+{
+ /* Nothing to do */
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_tar.c
@@ -0,0 +1,1306 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_tar.c
+ *
+ * This file is copied from the 'files' format file, but dumps data into
+ * one temp file then sends it to the output TAR archive.
+ *
+ * The tar format also includes a 'restore.sql' script which is there for
+ * the benefit of humans. This script is never used by pg_restore.
+ *
+ * NOTE: If you untar the created 'tar' file, the resulting files are
+ * compatible with the 'directory' format. Please keep the two formats in
+ * sync.
+ *
+ * See the headers to pg_backup_directory & pg_restore for more details.
+ *
+ * Copyright (c) 2000, Philip Warner
+ * Rights are granted to use this software in any way so long
+ * as this notice is not removed.
+ *
+ * The author is not responsible for loss or damages that may
+ * result from it's use.
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_backup_tar.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "pg_backup.h"
+#include "pg_backup_archiver.h"
+#include "pg_backup_tar.h"
+#include "pg_backup_utils.h"
+#include "parallel.h"
+#include "pgtar.h"
+
+#include <sys/stat.h>
+#include <ctype.h>
+#include <limits.h>
+#include <unistd.h>
+
+static void _ArchiveEntry(ArchiveHandle *AH, TocEntry *te);
+static void _StartData(ArchiveHandle *AH, TocEntry *te);
+static void _WriteData(ArchiveHandle *AH, const void *data, size_t dLen);
+static void _EndData(ArchiveHandle *AH, TocEntry *te);
+static int _WriteByte(ArchiveHandle *AH, const int i);
+static int _ReadByte(ArchiveHandle *);
+static void _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len);
+static void _ReadBuf(ArchiveHandle *AH, void *buf, size_t len);
+static void _CloseArchive(ArchiveHandle *AH);
+static void _PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt);
+static void _WriteExtraToc(ArchiveHandle *AH, TocEntry *te);
+static void _ReadExtraToc(ArchiveHandle *AH, TocEntry *te);
+static void _PrintExtraToc(ArchiveHandle *AH, TocEntry *te);
+
+static void _StartBlobs(ArchiveHandle *AH, TocEntry *te);
+static void _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+static void _EndBlobs(ArchiveHandle *AH, TocEntry *te);
+
+#define K_STD_BUF_SIZE 1024
+
+
+typedef struct
+{
+#ifdef HAVE_LIBZ
+ gzFile zFH;
+#else
+ FILE *zFH;
+#endif
+ FILE *nFH;
+ FILE *tarFH;
+ FILE *tmpFH;
+ char *targetFile;
+ char mode;
+ pgoff_t pos;
+ pgoff_t fileLen;
+ ArchiveHandle *AH;
+} TAR_MEMBER;
+
+typedef struct
+{
+ int hasSeek;
+ pgoff_t filePos;
+ TAR_MEMBER *blobToc;
+ FILE *tarFH;
+ pgoff_t tarFHpos;
+ pgoff_t tarNextMember;
+ TAR_MEMBER *FH;
+ int isSpecialScript;
+ TAR_MEMBER *scriptTH;
+} lclContext;
+
+typedef struct
+{
+ TAR_MEMBER *TH;
+ char *filename;
+} lclTocEntry;
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("tar archiver");
+
+static void _LoadBlobs(ArchiveHandle *AH, RestoreOptions *ropt);
+
+static TAR_MEMBER *tarOpen(ArchiveHandle *AH, const char *filename, char mode);
+static void tarClose(ArchiveHandle *AH, TAR_MEMBER *TH);
+
+#ifdef __NOT_USED__
+static char *tarGets(char *buf, size_t len, TAR_MEMBER *th);
+#endif
+static int tarPrintf(ArchiveHandle *AH, TAR_MEMBER *th, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
+
+static void _tarAddFile(ArchiveHandle *AH, TAR_MEMBER *th);
+static TAR_MEMBER *_tarPositionTo(ArchiveHandle *AH, const char *filename);
+static size_t tarRead(void *buf, size_t len, TAR_MEMBER *th);
+static size_t tarWrite(const void *buf, size_t len, TAR_MEMBER *th);
+static void _tarWriteHeader(TAR_MEMBER *th);
+static int _tarGetHeader(ArchiveHandle *AH, TAR_MEMBER *th);
+static size_t _tarReadRaw(ArchiveHandle *AH, void *buf, size_t len, TAR_MEMBER *th, FILE *fh);
+
+static size_t _scriptOut(ArchiveHandle *AH, const void *buf, size_t len);
+
+/*
+ * Initializer
+ */
+void
+InitArchiveFmt_Tar(ArchiveHandle *AH)
+{
+ lclContext *ctx;
+
+ /* Assuming static functions, this can be copied for each format. */
+ AH->ArchiveEntryPtr = _ArchiveEntry;
+ AH->StartDataPtr = _StartData;
+ AH->WriteDataPtr = _WriteData;
+ AH->EndDataPtr = _EndData;
+ AH->WriteBytePtr = _WriteByte;
+ AH->ReadBytePtr = _ReadByte;
+ AH->WriteBufPtr = _WriteBuf;
+ AH->ReadBufPtr = _ReadBuf;
+ AH->ClosePtr = _CloseArchive;
+ AH->ReopenPtr = NULL;
+ AH->PrintTocDataPtr = _PrintTocData;
+ AH->ReadExtraTocPtr = _ReadExtraToc;
+ AH->WriteExtraTocPtr = _WriteExtraToc;
+ AH->PrintExtraTocPtr = _PrintExtraToc;
+
+ AH->StartBlobsPtr = _StartBlobs;
+ AH->StartBlobPtr = _StartBlob;
+ AH->EndBlobPtr = _EndBlob;
+ AH->EndBlobsPtr = _EndBlobs;
+ AH->ClonePtr = NULL;
+ AH->DeClonePtr = NULL;
+
+ AH->MasterStartParallelItemPtr = NULL;
+ AH->MasterEndParallelItemPtr = NULL;
+
+ AH->WorkerJobDumpPtr = NULL;
+ AH->WorkerJobRestorePtr = NULL;
+
+ /*
+ * Set up some special context used in compressing data.
+ */
+ ctx = (lclContext *) pg_malloc0(sizeof(lclContext));
+ AH->formatData = (void *) ctx;
+ ctx->filePos = 0;
+ ctx->isSpecialScript = 0;
+
+ /* Initialize LO buffering */
+ AH->lo_buf_size = LOBBUFSIZE;
+ AH->lo_buf = (void *) pg_malloc(LOBBUFSIZE);
+
+ /*
+ * Now open the tar file, and load the TOC if we're in read mode.
+ */
+ if (AH->mode == archModeWrite)
+ {
+ if (AH->fSpec && strcmp(AH->fSpec, "") != 0)
+ {
+ ctx->tarFH = fopen(AH->fSpec, PG_BINARY_W);
+ if (ctx->tarFH == NULL)
+ exit_horribly(modulename,
+ "could not open TOC file \"%s\" for output: %s\n",
+ AH->fSpec, strerror(errno));
+ }
+ else
+ {
+ ctx->tarFH = stdout;
+ if (ctx->tarFH == NULL)
+ exit_horribly(modulename,
+ "could not open TOC file for output: %s\n",
+ strerror(errno));
+ }
+
+ ctx->tarFHpos = 0;
+
+ /*
+ * Make unbuffered since we will dup() it, and the buffers screw each
+ * other
+ */
+ /* setvbuf(ctx->tarFH, NULL, _IONBF, 0); */
+
+ ctx->hasSeek = checkSeek(ctx->tarFH);
+
+ /*
+ * We don't support compression because reading the files back is not
+ * possible since gzdopen uses buffered IO which totally screws file
+ * positioning.
+ */
+ if (AH->compression != 0)
+ exit_horribly(modulename,
+ "compression is not supported by tar archive format\n");
+ }
+ else
+ { /* Read Mode */
+ if (AH->fSpec && strcmp(AH->fSpec, "") != 0)
+ {
+ ctx->tarFH = fopen(AH->fSpec, PG_BINARY_R);
+ if (ctx->tarFH == NULL)
+ exit_horribly(modulename, "could not open TOC file \"%s\" for input: %s\n",
+ AH->fSpec, strerror(errno));
+ }
+ else
+ {
+ ctx->tarFH = stdin;
+ if (ctx->tarFH == NULL)
+ exit_horribly(modulename, "could not open TOC file for input: %s\n",
+ strerror(errno));
+ }
+
+ /*
+ * Make unbuffered since we will dup() it, and the buffers screw each
+ * other
+ */
+ /* setvbuf(ctx->tarFH, NULL, _IONBF, 0); */
+
+ ctx->tarFHpos = 0;
+
+ ctx->hasSeek = checkSeek(ctx->tarFH);
+
+ /*
+ * Forcibly unmark the header as read since we use the lookahead
+ * buffer
+ */
+ AH->readHeader = 0;
+
+ ctx->FH = (void *) tarOpen(AH, "toc.dat", 'r');
+ ReadHead(AH);
+ ReadToc(AH);
+ tarClose(AH, ctx->FH); /* Nothing else in the file... */
+ }
+}
+
+/*
+ * - Start a new TOC entry
+ * Setup the output file name.
+ */
+static void
+_ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx;
+ char fn[K_STD_BUF_SIZE];
+
+ ctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ if (te->dataDumper != NULL)
+ {
+#ifdef HAVE_LIBZ
+ if (AH->compression == 0)
+ sprintf(fn, "%d.dat", te->dumpId);
+ else
+ sprintf(fn, "%d.dat.gz", te->dumpId);
+#else
+ sprintf(fn, "%d.dat", te->dumpId);
+#endif
+ ctx->filename = pg_strdup(fn);
+ }
+ else
+ {
+ ctx->filename = NULL;
+ ctx->TH = NULL;
+ }
+ te->formatData = (void *) ctx;
+}
+
+static void
+_WriteExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx = (lclTocEntry *) te->formatData;
+
+ if (ctx->filename)
+ WriteStr(AH, ctx->filename);
+ else
+ WriteStr(AH, "");
+}
+
+static void
+_ReadExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx = (lclTocEntry *) te->formatData;
+
+ if (ctx == NULL)
+ {
+ ctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ te->formatData = (void *) ctx;
+ }
+
+ ctx->filename = ReadStr(AH);
+ if (strlen(ctx->filename) == 0)
+ {
+ free(ctx->filename);
+ ctx->filename = NULL;
+ }
+ ctx->TH = NULL;
+}
+
+static void
+_PrintExtraToc(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *ctx = (lclTocEntry *) te->formatData;
+
+ if (AH->public.verbose && ctx->filename != NULL)
+ ahprintf(AH, "-- File: %s\n", ctx->filename);
+}
+
+static void
+_StartData(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ tctx->TH = tarOpen(AH, tctx->filename, 'w');
+}
+
+static TAR_MEMBER *
+tarOpen(ArchiveHandle *AH, const char *filename, char mode)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ TAR_MEMBER *tm;
+
+#ifdef HAVE_LIBZ
+ char fmode[10];
+#endif
+
+ if (mode == 'r')
+ {
+ tm = _tarPositionTo(AH, filename);
+ if (!tm) /* Not found */
+ {
+ if (filename)
+ {
+ /*
+ * Couldn't find the requested file. Future: do SEEK(0) and
+ * retry.
+ */
+ exit_horribly(modulename, "could not find file \"%s\" in archive\n", filename);
+ }
+ else
+ {
+ /* Any file OK, none left, so return NULL */
+ return NULL;
+ }
+ }
+
+#ifdef HAVE_LIBZ
+
+ if (AH->compression == 0)
+ tm->nFH = ctx->tarFH;
+ else
+ exit_horribly(modulename, "compression is not supported by tar archive format\n");
+ /* tm->zFH = gzdopen(dup(fileno(ctx->tarFH)), "rb"); */
+#else
+ tm->nFH = ctx->tarFH;
+#endif
+ }
+ else
+ {
+ int old_umask;
+
+ tm = pg_malloc0(sizeof(TAR_MEMBER));
+
+ /*
+ * POSIX does not require, but permits, tmpfile() to restrict file
+ * permissions. Given an OS crash after we write data, the filesystem
+ * might retain the data but forget tmpfile()'s unlink(). If so, the
+ * file mode protects confidentiality of the data written.
+ */
+ old_umask = umask(S_IRWXG | S_IRWXO);
+
+#ifndef WIN32
+ tm->tmpFH = tmpfile();
+#else
+
+ /*
+ * On WIN32, tmpfile() generates a filename in the root directory,
+ * which requires administrative permissions on certain systems. Loop
+ * until we find a unique file name we can create.
+ */
+ while (1)
+ {
+ char *name;
+ int fd;
+
+ name = _tempnam(NULL, "pg_temp_");
+ if (name == NULL)
+ break;
+ fd = open(name, O_RDWR | O_CREAT | O_EXCL | O_BINARY |
+ O_TEMPORARY, S_IRUSR | S_IWUSR);
+ free(name);
+
+ if (fd != -1) /* created a file */
+ {
+ tm->tmpFH = fdopen(fd, "w+b");
+ break;
+ }
+ else if (errno != EEXIST) /* failure other than file exists */
+ break;
+ }
+#endif
+
+ if (tm->tmpFH == NULL)
+ exit_horribly(modulename, "could not generate temporary file name: %s\n", strerror(errno));
+
+ umask(old_umask);
+
+#ifdef HAVE_LIBZ
+
+ if (AH->compression != 0)
+ {
+ sprintf(fmode, "wb%d", AH->compression);
+ tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
+ if (tm->zFH == NULL)
+ exit_horribly(modulename, "could not open temporary file\n");
+ }
+ else
+ tm->nFH = tm->tmpFH;
+#else
+
+ tm->nFH = tm->tmpFH;
+#endif
+
+ tm->AH = AH;
+ tm->targetFile = pg_strdup(filename);
+ }
+
+ tm->mode = mode;
+ tm->tarFH = ctx->tarFH;
+
+ return tm;
+}
+
+static void
+tarClose(ArchiveHandle *AH, TAR_MEMBER *th)
+{
+ /*
+ * Close the GZ file since we dup'd. This will flush the buffers.
+ */
+ if (AH->compression != 0)
+ if (GZCLOSE(th->zFH) != 0)
+ exit_horribly(modulename, "could not close tar member\n");
+
+ if (th->mode == 'w')
+ _tarAddFile(AH, th); /* This will close the temp file */
+
+ /*
+ * else Nothing to do for normal read since we don't dup() normal file
+ * handle, and we don't use temp files.
+ */
+
+ if (th->targetFile)
+ free(th->targetFile);
+
+ th->nFH = NULL;
+ th->zFH = NULL;
+}
+
+#ifdef __NOT_USED__
+static char *
+tarGets(char *buf, size_t len, TAR_MEMBER *th)
+{
+ char *s;
+ size_t cnt = 0;
+ char c = ' ';
+ int eof = 0;
+
+ /* Can't read past logical EOF */
+ if (len > (th->fileLen - th->pos))
+ len = th->fileLen - th->pos;
+
+ while (cnt < len && c != '\n')
+ {
+ if (_tarReadRaw(th->AH, &c, 1, th, NULL) <= 0)
+ {
+ eof = 1;
+ break;
+ }
+ buf[cnt++] = c;
+ }
+
+ if (eof && cnt == 0)
+ s = NULL;
+ else
+ {
+ buf[cnt++] = '\0';
+ s = buf;
+ }
+
+ if (s)
+ {
+ len = strlen(s);
+ th->pos += len;
+ }
+
+ return s;
+}
+#endif
+
+/*
+ * Just read bytes from the archive. This is the low level read routine
+ * that is used for ALL reads on a tar file.
+ */
+static size_t
+_tarReadRaw(ArchiveHandle *AH, void *buf, size_t len, TAR_MEMBER *th, FILE *fh)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ size_t avail;
+ size_t used = 0;
+ size_t res = 0;
+
+ avail = AH->lookaheadLen - AH->lookaheadPos;
+ if (avail > 0)
+ {
+ /* We have some lookahead bytes to use */
+ if (avail >= len) /* Just use the lookahead buffer */
+ used = len;
+ else
+ used = avail;
+
+ /* Copy, and adjust buffer pos */
+ memcpy(buf, AH->lookahead + AH->lookaheadPos, used);
+ AH->lookaheadPos += used;
+
+ /* Adjust required length */
+ len -= used;
+ }
+
+ /* Read the file if len > 0 */
+ if (len > 0)
+ {
+ if (fh)
+ {
+ res = fread(&((char *) buf)[used], 1, len, fh);
+ if (res != len && !feof(fh))
+ READ_ERROR_EXIT(fh);
+ }
+ else if (th)
+ {
+ if (th->zFH)
+ {
+ res = GZREAD(&((char *) buf)[used], 1, len, th->zFH);
+ if (res != len && !GZEOF(th->zFH))
+ exit_horribly(modulename,
+ "could not read from input file: %s\n", strerror(errno));
+ }
+ else
+ {
+ res = fread(&((char *) buf)[used], 1, len, th->nFH);
+ if (res != len && !feof(th->nFH))
+ READ_ERROR_EXIT(th->nFH);
+ }
+ }
+ else
+ exit_horribly(modulename, "internal error -- neither th nor fh specified in tarReadRaw()\n");
+ }
+
+ ctx->tarFHpos += res + used;
+
+ return (res + used);
+}
+
+static size_t
+tarRead(void *buf, size_t len, TAR_MEMBER *th)
+{
+ size_t res;
+
+ if (th->pos + len > th->fileLen)
+ len = th->fileLen - th->pos;
+
+ if (len <= 0)
+ return 0;
+
+ res = _tarReadRaw(th->AH, buf, len, th, NULL);
+
+ th->pos += res;
+
+ return res;
+}
+
+static size_t
+tarWrite(const void *buf, size_t len, TAR_MEMBER *th)
+{
+ size_t res;
+
+ if (th->zFH != NULL)
+ res = GZWRITE(buf, 1, len, th->zFH);
+ else
+ res = fwrite(buf, 1, len, th->nFH);
+
+ th->pos += res;
+ return res;
+}
+
+static void
+_WriteData(ArchiveHandle *AH, const void *data, size_t dLen)
+{
+ lclTocEntry *tctx = (lclTocEntry *) AH->currToc->formatData;
+
+ if (tarWrite(data, dLen, tctx->TH) != dLen)
+ WRITE_ERROR_EXIT;
+
+ return;
+}
+
+static void
+_EndData(ArchiveHandle *AH, TocEntry *te)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ /* Close the file */
+ tarClose(AH, tctx->TH);
+ tctx->TH = NULL;
+}
+
+/*
+ * Print data for a given file
+ */
+static void
+_PrintFileData(ArchiveHandle *AH, char *filename, RestoreOptions *ropt)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char buf[4096];
+ size_t cnt;
+ TAR_MEMBER *th;
+
+ if (!filename)
+ return;
+
+ th = tarOpen(AH, filename, 'r');
+ ctx->FH = th;
+
+ while ((cnt = tarRead(buf, 4095, th)) > 0)
+ {
+ buf[cnt] = '\0';
+ ahwrite(buf, 1, cnt, AH);
+ }
+
+ tarClose(AH, th);
+}
+
+
+/*
+ * Print data for a given TOC entry
+*/
+static void
+_PrintTocData(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+ int pos1;
+
+ if (!tctx->filename)
+ return;
+
+ /*
+ * If we're writing the special restore.sql script, emit a suitable
+ * command to include each table's data from the corresponding file.
+ *
+ * In the COPY case this is a bit klugy because the regular COPY command
+ * was already printed before we get control.
+ */
+ if (ctx->isSpecialScript)
+ {
+ if (te->copyStmt)
+ {
+ /* Abort the COPY FROM stdin */
+ ahprintf(AH, "\\.\n");
+
+ /*
+ * The COPY statement should look like "COPY ... FROM stdin;\n",
+ * see dumpTableData().
+ */
+ pos1 = (int) strlen(te->copyStmt) - 13;
+ if (pos1 < 6 || strncmp(te->copyStmt, "COPY ", 5) != 0 ||
+ strcmp(te->copyStmt + pos1, " FROM stdin;\n") != 0)
+ exit_horribly(modulename,
+ "unexpected COPY statement syntax: \"%s\"\n",
+ te->copyStmt);
+
+ /* Emit all but the FROM part ... */
+ ahwrite(te->copyStmt, 1, pos1, AH);
+ /* ... and insert modified FROM */
+ ahprintf(AH, " FROM '$$PATH$$/%s';\n\n", tctx->filename);
+ }
+ else
+ {
+ /* --inserts mode, no worries, just include the data file */
+ ahprintf(AH, "\\i $$PATH$$/%s\n\n", tctx->filename);
+ }
+
+ return;
+ }
+
+ if (strcmp(te->desc, "BLOBS") == 0)
+ _LoadBlobs(AH, ropt);
+ else
+ _PrintFileData(AH, tctx->filename, ropt);
+}
+
+static void
+_LoadBlobs(ArchiveHandle *AH, RestoreOptions *ropt)
+{
+ Oid oid;
+ lclContext *ctx = (lclContext *) AH->formatData;
+ TAR_MEMBER *th;
+ size_t cnt;
+ bool foundBlob = false;
+ char buf[4096];
+
+ StartRestoreBlobs(AH);
+
+ th = tarOpen(AH, NULL, 'r'); /* Open next file */
+ while (th != NULL)
+ {
+ ctx->FH = th;
+
+ if (strncmp(th->targetFile, "blob_", 5) == 0)
+ {
+ oid = atooid(&th->targetFile[5]);
+ if (oid != 0)
+ {
+ ahlog(AH, 1, "restoring large object with OID %u\n", oid);
+
+ StartRestoreBlob(AH, oid, ropt->dropSchema);
+
+ while ((cnt = tarRead(buf, 4095, th)) > 0)
+ {
+ buf[cnt] = '\0';
+ ahwrite(buf, 1, cnt, AH);
+ }
+ EndRestoreBlob(AH, oid);
+ foundBlob = true;
+ }
+ tarClose(AH, th);
+ }
+ else
+ {
+ tarClose(AH, th);
+
+ /*
+ * Once we have found the first blob, stop at the first non-blob
+ * entry (which will be 'blobs.toc'). This coding would eat all
+ * the rest of the archive if there are no blobs ... but this
+ * function shouldn't be called at all in that case.
+ */
+ if (foundBlob)
+ break;
+ }
+
+ th = tarOpen(AH, NULL, 'r');
+ }
+ EndRestoreBlobs(AH);
+}
+
+
+static int
+_WriteByte(ArchiveHandle *AH, const int i)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char b = i; /* Avoid endian problems */
+
+ if (tarWrite(&b, 1, ctx->FH) != 1)
+ WRITE_ERROR_EXIT;
+
+ ctx->filePos += 1;
+ return 1;
+}
+
+static int
+_ReadByte(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ size_t res;
+ unsigned char c;
+
+ res = tarRead(&c, 1, ctx->FH);
+ if (res != 1)
+ /* We already would have exited for errors on reads, must be EOF */
+ exit_horribly(modulename,
+ "could not read from input file: end of file\n");
+ ctx->filePos += 1;
+ return c;
+}
+
+static void
+_WriteBuf(ArchiveHandle *AH, const void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (tarWrite(buf, len, ctx->FH) != len)
+ WRITE_ERROR_EXIT;
+
+ ctx->filePos += len;
+}
+
+static void
+_ReadBuf(ArchiveHandle *AH, void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ if (tarRead(buf, len, ctx->FH) != len)
+ /* We already would have exited for errors on reads, must be EOF */
+ exit_horribly(modulename,
+ "could not read from input file: end of file\n");
+
+ ctx->filePos += len;
+ return;
+}
+
+static void
+_CloseArchive(ArchiveHandle *AH)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ TAR_MEMBER *th;
+ RestoreOptions *ropt;
+ RestoreOptions *savRopt;
+ int savVerbose,
+ i;
+
+ if (AH->mode == archModeWrite)
+ {
+ /*
+ * Write the Header & TOC to the archive FIRST
+ */
+ th = tarOpen(AH, "toc.dat", 'w');
+ ctx->FH = th;
+ WriteHead(AH);
+ WriteToc(AH);
+ tarClose(AH, th); /* Not needed any more */
+
+ /*
+ * Now send the data (tables & blobs)
+ */
+ WriteDataChunks(AH, NULL);
+
+ /*
+ * Now this format wants to append a script which does a full restore
+ * if the files have been extracted.
+ */
+ th = tarOpen(AH, "restore.sql", 'w');
+
+ tarPrintf(AH, th, "--\n"
+ "-- NOTE:\n"
+ "--\n"
+ "-- File paths need to be edited. Search for $$PATH$$ and\n"
+ "-- replace it with the path to the directory containing\n"
+ "-- the extracted data files.\n"
+ "--\n");
+
+ AH->CustomOutPtr = _scriptOut;
+
+ ctx->isSpecialScript = 1;
+ ctx->scriptTH = th;
+
+ ropt = NewRestoreOptions();
+ memcpy(ropt, AH->ropt, sizeof(RestoreOptions));
+ ropt->filename = NULL;
+ ropt->dropSchema = 1;
+ ropt->compression = 0;
+ ropt->superuser = NULL;
+ ropt->suppressDumpWarnings = true;
+
+ savRopt = AH->ropt;
+ AH->ropt = ropt;
+
+ savVerbose = AH->public.verbose;
+ AH->public.verbose = 0;
+
+ RestoreArchive((Archive *) AH);
+
+ AH->ropt = savRopt;
+ AH->public.verbose = savVerbose;
+
+ tarClose(AH, th);
+
+ ctx->isSpecialScript = 0;
+
+ /*
+ * EOF marker for tar files is two blocks of NULLs.
+ */
+ for (i = 0; i < 512 * 2; i++)
+ {
+ if (fputc(0, ctx->tarFH) == EOF)
+ WRITE_ERROR_EXIT;
+ }
+ }
+
+ AH->FH = NULL;
+}
+
+static size_t
+_scriptOut(ArchiveHandle *AH, const void *buf, size_t len)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ return tarWrite(buf, len, ctx->scriptTH);
+}
+
+/*
+ * BLOB support
+ */
+
+/*
+ * Called by the archiver when starting to save all BLOB DATA (not schema).
+ * This routine should save whatever format-specific information is needed
+ * to read the BLOBs back into memory.
+ *
+ * It is called just prior to the dumper's DataDumper routine.
+ *
+ * Optional, but strongly recommended.
+ *
+ */
+static void
+_StartBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char fname[K_STD_BUF_SIZE];
+
+ sprintf(fname, "blobs.toc");
+ ctx->blobToc = tarOpen(AH, fname, 'w');
+}
+
+/*
+ * Called by the archiver when the dumper calls StartBlob.
+ *
+ * Mandatory.
+ *
+ * Must save the passed OID for retrieval at restore-time.
+ */
+static void
+_StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+ char fname[255];
+ char *sfx;
+
+ if (oid == 0)
+ exit_horribly(modulename, "invalid OID for large object (%u)\n", oid);
+
+ if (AH->compression != 0)
+ sfx = ".gz";
+ else
+ sfx = "";
+
+ sprintf(fname, "blob_%u.dat%s", oid, sfx);
+
+ tarPrintf(AH, ctx->blobToc, "%u %s\n", oid, fname);
+
+ tctx->TH = tarOpen(AH, fname, 'w');
+}
+
+/*
+ * Called by the archiver when the dumper calls EndBlob.
+ *
+ * Optional.
+ *
+ */
+static void
+_EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+{
+ lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+
+ tarClose(AH, tctx->TH);
+}
+
+/*
+ * Called by the archiver when finishing saving all BLOB DATA.
+ *
+ * Optional.
+ *
+ */
+static void
+_EndBlobs(ArchiveHandle *AH, TocEntry *te)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+
+ /* Write out a fake zero OID to mark end-of-blobs. */
+ /* WriteInt(AH, 0); */
+
+ tarClose(AH, ctx->blobToc);
+}
+
+
+
+/*------------
+ * TAR Support
+ *------------
+ */
+
+static int
+tarPrintf(ArchiveHandle *AH, TAR_MEMBER *th, const char *fmt,...)
+{
+ char *p;
+ size_t len = 128; /* initial assumption about buffer size */
+ size_t cnt;
+
+ for (;;)
+ {
+ va_list args;
+
+ /* Allocate work buffer. */
+ p = (char *) pg_malloc(len);
+
+ /* Try to format the data. */
+ va_start(args, fmt);
+ cnt = pvsnprintf(p, len, fmt, args);
+ va_end(args);
+
+ if (cnt < len)
+ break; /* success */
+
+ /* Release buffer and loop around to try again with larger len. */
+ free(p);
+ len = cnt;
+ }
+
+ cnt = tarWrite(p, cnt, th);
+ free(p);
+ return (int) cnt;
+}
+
+bool
+isValidTarHeader(char *header)
+{
+ int sum;
+ int chk = tarChecksum(header);
+
+ sum = read_tar_number(&header[148], 8);
+
+ if (sum != chk)
+ return false;
+
+ /* POSIX tar format */
+ if (memcmp(&header[257], "ustar\0", 6) == 0 &&
+ memcmp(&header[263], "00", 2) == 0)
+ return true;
+ /* GNU tar format */
+ if (memcmp(&header[257], "ustar \0", 8) == 0)
+ return true;
+ /* not-quite-POSIX format written by pre-9.3 pg_dump */
+ if (memcmp(&header[257], "ustar00\0", 8) == 0)
+ return true;
+
+ return false;
+}
+
+/* Given the member, write the TAR header & copy the file */
+static void
+_tarAddFile(ArchiveHandle *AH, TAR_MEMBER *th)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ FILE *tmp = th->tmpFH; /* Grab it for convenience */
+ char buf[32768];
+ size_t cnt;
+ pgoff_t len = 0;
+ size_t res;
+ size_t i,
+ pad;
+
+ /*
+ * Find file len & go back to start.
+ */
+ fseeko(tmp, 0, SEEK_END);
+ th->fileLen = ftello(tmp);
+ if (th->fileLen < 0)
+ exit_horribly(modulename, "could not determine seek position in archive file: %s\n",
+ strerror(errno));
+ fseeko(tmp, 0, SEEK_SET);
+
+ _tarWriteHeader(th);
+
+ while ((cnt = fread(buf, 1, sizeof(buf), tmp)) > 0)
+ {
+ if ((res = fwrite(buf, 1, cnt, th->tarFH)) != cnt)
+ WRITE_ERROR_EXIT;
+ len += res;
+ }
+ if (!feof(tmp))
+ READ_ERROR_EXIT(tmp);
+
+ if (fclose(tmp) != 0) /* This *should* delete it... */
+ exit_horribly(modulename, "could not close temporary file: %s\n",
+ strerror(errno));
+
+ if (len != th->fileLen)
+ {
+ char buf1[32],
+ buf2[32];
+
+ snprintf(buf1, sizeof(buf1), INT64_FORMAT, (int64) len);
+ snprintf(buf2, sizeof(buf2), INT64_FORMAT, (int64) th->fileLen);
+ exit_horribly(modulename, "actual file length (%s) does not match expected (%s)\n",
+ buf1, buf2);
+ }
+
+ pad = ((len + 511) & ~511) - len;
+ for (i = 0; i < pad; i++)
+ {
+ if (fputc('\0', th->tarFH) == EOF)
+ WRITE_ERROR_EXIT;
+ }
+
+ ctx->tarFHpos += len + pad;
+}
+
+/* Locate the file in the archive, read header and position to data */
+static TAR_MEMBER *
+_tarPositionTo(ArchiveHandle *AH, const char *filename)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ TAR_MEMBER *th = pg_malloc0(sizeof(TAR_MEMBER));
+ char c;
+ char header[512];
+ size_t i,
+ len,
+ blks;
+ int id;
+
+ th->AH = AH;
+
+ /* Go to end of current file, if any */
+ if (ctx->tarFHpos != 0)
+ {
+ char buf1[100],
+ buf2[100];
+
+ snprintf(buf1, sizeof(buf1), INT64_FORMAT, (int64) ctx->tarFHpos);
+ snprintf(buf2, sizeof(buf2), INT64_FORMAT, (int64) ctx->tarNextMember);
+ ahlog(AH, 4, "moving from position %s to next member at file position %s\n",
+ buf1, buf2);
+
+ while (ctx->tarFHpos < ctx->tarNextMember)
+ _tarReadRaw(AH, &c, 1, NULL, ctx->tarFH);
+ }
+
+ {
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), INT64_FORMAT, (int64) ctx->tarFHpos);
+ ahlog(AH, 4, "now at file position %s\n", buf);
+ }
+
+ /* We are at the start of the file, or at the next member */
+
+ /* Get the header */
+ if (!_tarGetHeader(AH, th))
+ {
+ if (filename)
+ exit_horribly(modulename, "could not find header for file \"%s\" in tar archive\n", filename);
+ else
+ {
+ /*
+ * We're just scanning the archive for the next file, so return
+ * null
+ */
+ free(th);
+ return NULL;
+ }
+ }
+
+ while (filename != NULL && strcmp(th->targetFile, filename) != 0)
+ {
+ ahlog(AH, 4, "skipping tar member %s\n", th->targetFile);
+
+ id = atoi(th->targetFile);
+ if ((TocIDRequired(AH, id) & REQ_DATA) != 0)
+ exit_horribly(modulename, "restoring data out of order is not supported in this archive format: "
+ "\"%s\" is required, but comes before \"%s\" in the archive file.\n",
+ th->targetFile, filename);
+
+ /* Header doesn't match, so read to next header */
+ len = ((th->fileLen + 511) & ~511); /* Padded length */
+ blks = len >> 9; /* # of 512 byte blocks */
+
+ for (i = 0; i < blks; i++)
+ _tarReadRaw(AH, &header[0], 512, NULL, ctx->tarFH);
+
+ if (!_tarGetHeader(AH, th))
+ exit_horribly(modulename, "could not find header for file \"%s\" in tar archive\n", filename);
+ }
+
+ ctx->tarNextMember = ctx->tarFHpos + ((th->fileLen + 511) & ~511);
+ th->pos = 0;
+
+ return th;
+}
+
+/* Read & verify a header */
+static int
+_tarGetHeader(ArchiveHandle *AH, TAR_MEMBER *th)
+{
+ lclContext *ctx = (lclContext *) AH->formatData;
+ char h[512];
+ char tag[100 + 1];
+ int sum,
+ chk;
+ pgoff_t len;
+ pgoff_t hPos;
+ bool gotBlock = false;
+
+ while (!gotBlock)
+ {
+ /* Save the pos for reporting purposes */
+ hPos = ctx->tarFHpos;
+
+ /* Read a 512 byte block, return EOF, exit if short */
+ len = _tarReadRaw(AH, h, 512, NULL, ctx->tarFH);
+ if (len == 0) /* EOF */
+ return 0;
+
+ if (len != 512)
+ exit_horribly(modulename,
+ ngettext("incomplete tar header found (%lu byte)\n",
+ "incomplete tar header found (%lu bytes)\n",
+ len),
+ (unsigned long) len);
+
+ /* Calc checksum */
+ chk = tarChecksum(h);
+ sum = read_tar_number(&h[148], 8);
+
+ /*
+ * If the checksum failed, see if it is a null block. If so, silently
+ * continue to the next block.
+ */
+ if (chk == sum)
+ gotBlock = true;
+ else
+ {
+ int i;
+
+ for (i = 0; i < 512; i++)
+ {
+ if (h[i] != 0)
+ {
+ gotBlock = true;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Name field is 100 bytes, might not be null-terminated */
+ strlcpy(tag, &h[0], 100 + 1);
+
+ len = read_tar_number(&h[124], 12);
+
+ {
+ char posbuf[32];
+ char lenbuf[32];
+
+ snprintf(posbuf, sizeof(posbuf), UINT64_FORMAT, (uint64) hPos);
+ snprintf(lenbuf, sizeof(lenbuf), UINT64_FORMAT, (uint64) len);
+ ahlog(AH, 3, "TOC Entry %s at %s (length %s, checksum %d)\n",
+ tag, posbuf, lenbuf, sum);
+ }
+
+ if (chk != sum)
+ {
+ char posbuf[32];
+
+ snprintf(posbuf, sizeof(posbuf), UINT64_FORMAT,
+ (uint64) ftello(ctx->tarFH));
+ exit_horribly(modulename,
+ "corrupt tar header found in %s "
+ "(expected %d, computed %d) file position %s\n",
+ tag, sum, chk, posbuf);
+ }
+
+ th->targetFile = pg_strdup(tag);
+ th->fileLen = len;
+
+ return 1;
+}
+
+
+static void
+_tarWriteHeader(TAR_MEMBER *th)
+{
+ char h[512];
+
+ tarCreateHeader(h, th->targetFile, NULL, th->fileLen,
+ 0600, 04000, 02000, time(NULL));
+
+ /* Now write the completed header. */
+ if (fwrite(h, 1, 512, th->tarFH) != 512)
+ WRITE_ERROR_EXIT;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_tar.h
@@ -0,0 +1,37 @@
+/*
+ * src/bin/pg_dump/pg_backup_tar.h
+ *
+ * TAR Header (see "ustar interchange format" in POSIX 1003.1)
+ *
+ * Offset Length Contents
+ * 0 100 bytes File name ('\0' terminated, 99 maximum length)
+ * 100 8 bytes File mode (in octal ascii)
+ * 108 8 bytes User ID (in octal ascii)
+ * 116 8 bytes Group ID (in octal ascii)
+ * 124 12 bytes File size (in octal ascii)
+ * 136 12 bytes Modify time (Unix timestamp in octal ascii)
+ * 148 8 bytes Header checksum (in octal ascii)
+ * 156 1 bytes Type flag (see below)
+ * 157 100 bytes Linkname, if symlink ('\0' terminated, 99 maximum length)
+ * 257 6 bytes Magic ("ustar\0")
+ * 263 2 bytes Version ("00")
+ * 265 32 bytes User name ('\0' terminated, 31 maximum length)
+ * 297 32 bytes Group name ('\0' terminated, 31 maximum length)
+ * 329 8 bytes Major device ID (in octal ascii)
+ * 337 8 bytes Minor device ID (in octal ascii)
+ * 345 155 bytes File name prefix (not used in our implementation)
+ * 500 12 bytes Padding
+ *
+ * 512 (s+p)bytes File contents, padded out to 512-byte boundary
+ */
+
+/* The type flag defines the type of file */
+#define LF_OLDNORMAL '\0' /* Normal disk file, Unix compatible */
+#define LF_NORMAL '0' /* Normal disk file */
+#define LF_LINK '1' /* Link to previously dumped file */
+#define LF_SYMLINK '2' /* Symbolic link */
+#define LF_CHR '3' /* Character special file */
+#define LF_BLK '4' /* Block special file */
+#define LF_DIR '5' /* Directory */
+#define LF_FIFO '6' /* FIFO special file */
+#define LF_CONTIG '7' /* Contiguous file */
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_utils.c
@@ -0,0 +1,126 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_utils.c
+ * Utility routines shared by pg_dump and pg_restore
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_dump/pg_backup_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+/* Globals exported by this file */
+const char *progname = NULL;
+
+#define MAX_ON_EXIT_NICELY 20
+
+static struct
+{
+ on_exit_nicely_callback function;
+ void *arg;
+} on_exit_nicely_list[MAX_ON_EXIT_NICELY];
+
+static int on_exit_nicely_index;
+
+/*
+ * Parse a --section=foo command line argument.
+ *
+ * Set or update the bitmask in *dumpSections according to arg.
+ * dumpSections is initialised as DUMP_UNSECTIONED by pg_dump and
+ * pg_restore so they can know if this has even been called.
+ */
+void
+set_dump_section(const char *arg, int *dumpSections)
+{
+ /* if this is the first call, clear all the bits */
+ if (*dumpSections == DUMP_UNSECTIONED)
+ *dumpSections = 0;
+
+ if (strcmp(arg, "pre-data") == 0)
+ *dumpSections |= DUMP_PRE_DATA;
+ else if (strcmp(arg, "data") == 0)
+ *dumpSections |= DUMP_DATA;
+ else if (strcmp(arg, "post-data") == 0)
+ *dumpSections |= DUMP_POST_DATA;
+ else
+ {
+ fprintf(stderr, _("%s: unrecognized section name: \"%s\"\n"),
+ progname, arg);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit_nicely(1);
+ }
+}
+
+
+/*
+ * Write a printf-style message to stderr.
+ *
+ * The program name is prepended, if "progname" has been set.
+ * Also, if modulename isn't NULL, that's included too.
+ * Note that we'll try to translate the modulename and the fmt string.
+ */
+void
+write_msg(const char *modulename, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwrite_msg(modulename, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * As write_msg, but pass a va_list not variable arguments.
+ */
+void
+vwrite_msg(const char *modulename, const char *fmt, va_list ap)
+{
+ if (progname)
+ {
+ if (modulename)
+ fprintf(stderr, "%s: [%s] ", progname, _(modulename));
+ else
+ fprintf(stderr, "%s: ", progname);
+ }
+ vfprintf(stderr, _(fmt), ap);
+}
+
+/* Register a callback to be run when exit_nicely is invoked. */
+void
+on_exit_nicely(on_exit_nicely_callback function, void *arg)
+{
+ if (on_exit_nicely_index >= MAX_ON_EXIT_NICELY)
+ exit_horribly(NULL, "out of on_exit_nicely slots\n");
+ on_exit_nicely_list[on_exit_nicely_index].function = function;
+ on_exit_nicely_list[on_exit_nicely_index].arg = arg;
+ on_exit_nicely_index++;
+}
+
+/*
+ * Run accumulated on_exit_nicely callbacks in reverse order and then exit
+ * quietly. This needs to be thread-safe.
+ */
+void
+exit_nicely(int code)
+{
+ int i;
+
+ for (i = on_exit_nicely_index - 1; i >= 0; i--)
+ (*on_exit_nicely_list[i].function) (code,
+ on_exit_nicely_list[i].arg);
+
+#ifdef WIN32
+ if (parallel_init_done && GetCurrentThreadId() != mainThreadId)
+ ExitThread(code);
+#endif
+
+ exit(code);
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_backup_utils.h
@@ -0,0 +1,40 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_backup_utils.h
+ * Utility routines shared by pg_dump and pg_restore.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_dump/pg_backup_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PG_BACKUP_UTILS_H
+#define PG_BACKUP_UTILS_H
+
+typedef enum /* bits returned by set_dump_section */
+{
+ DUMP_PRE_DATA = 0x01,
+ DUMP_DATA = 0x02,
+ DUMP_POST_DATA = 0x04,
+ DUMP_UNSECTIONED = 0xff
+} DumpSections;
+
+typedef void (*on_exit_nicely_callback) (int code, void *arg);
+
+extern const char *progname;
+
+extern void set_dump_section(const char *arg, int *dumpSections);
+extern void
+write_msg(const char *modulename, const char *fmt,...)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+extern void
+vwrite_msg(const char *modulename, const char *fmt, va_list ap)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 0)));
+extern void on_exit_nicely(on_exit_nicely_callback function, void *arg);
+extern void exit_nicely(int code) __attribute__((noreturn));
+
+#endif /* PG_BACKUP_UTILS_H */
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_dump.c
@@ -0,0 +1,15811 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_dump.c
+ * pg_dump is a utility for dumping out a postgres database
+ * into a script file.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * pg_dump will read the system catalogs in a database and dump out a
+ * script that reproduces the schema in terms of SQL that is understood
+ * by PostgreSQL
+ *
+ * Note that pg_dump runs in a transaction-snapshot mode transaction,
+ * so it sees a consistent snapshot of the database including system
+ * catalogs. However, it relies in part on various specialized backend
+ * functions like pg_get_indexdef(), and those things tend to look at
+ * the currently committed state. So it is possible to get 'cache
+ * lookup failed' error if someone performs DDL changes while a dump is
+ * happening. The window for this sort of thing is from the acquisition
+ * of the transaction snapshot to getSchemaData() (when pg_dump acquires
+ * AccessShareLock on every table it intends to dump). It isn't very large,
+ * but it can happen.
+ *
+ * http://archives.postgresql.org/pgsql-bugs/2010-02/msg00187.php
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_dump.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <unistd.h>
+#include <ctype.h>
+#ifdef ENABLE_NLS
+#include <locale.h>
+#endif
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#include "getopt_long.h"
+
+#include "access/attnum.h"
+#include "access/sysattr.h"
+#include "access/transam.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_default_acl.h"
+#include "catalog/pg_event_trigger.h"
+#include "catalog/pg_largeobject.h"
+#include "catalog/pg_largeobject_metadata.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "libpq/libpq-fs.h"
+
+#include "pg_backup_archiver.h"
+#include "pg_backup_db.h"
+#include "pg_backup_utils.h"
+#include "dumputils.h"
+#include "parallel.h"
+
+
+typedef struct
+{
+ const char *descr; /* comment for an object */
+ Oid classoid; /* object class (catalog OID) */
+ Oid objoid; /* object OID */
+ int objsubid; /* subobject (table column #) */
+} CommentItem;
+
+typedef struct
+{
+ const char *provider; /* label provider of this security label */
+ const char *label; /* security label for an object */
+ Oid classoid; /* object class (catalog OID) */
+ Oid objoid; /* object OID */
+ int objsubid; /* subobject (table column #) */
+} SecLabelItem;
+
+/* global decls */
+bool g_verbose; /* User wants verbose narration of our
+ * activities. */
+
+/* various user-settable parameters */
+static bool schemaOnly;
+static bool dataOnly;
+static int dumpSections; /* bitmask of chosen sections */
+static bool aclsSkip;
+static const char *lockWaitTimeout;
+
+/* subquery used to convert user ID (eg, datdba) to user name */
+static const char *username_subquery;
+
+/* obsolete as of 7.3: */
+static Oid g_last_builtin_oid; /* value of the last builtin oid */
+
+/*
+ * Object inclusion/exclusion lists
+ *
+ * The string lists record the patterns given by command-line switches,
+ * which we then convert to lists of OIDs of matching objects.
+ */
+static SimpleStringList schema_include_patterns = {NULL, NULL};
+static SimpleOidList schema_include_oids = {NULL, NULL};
+static SimpleStringList schema_exclude_patterns = {NULL, NULL};
+static SimpleOidList schema_exclude_oids = {NULL, NULL};
+
+static SimpleStringList table_include_patterns = {NULL, NULL};
+static SimpleOidList table_include_oids = {NULL, NULL};
+static SimpleStringList table_exclude_patterns = {NULL, NULL};
+static SimpleOidList table_exclude_oids = {NULL, NULL};
+static SimpleStringList tabledata_exclude_patterns = {NULL, NULL};
+static SimpleOidList tabledata_exclude_oids = {NULL, NULL};
+
+/* default, if no "inclusion" switches appear, is to dump everything */
+static bool include_everything = true;
+
+char g_opaque_type[10]; /* name for the opaque type */
+
+/* placeholders for the delimiters for comments */
+char g_comment_start[10];
+char g_comment_end[10];
+
+static const CatalogId nilCatalogId = {0, 0};
+
+/* flags for various command-line long options */
+static int binary_upgrade = 0;
+static int disable_dollar_quoting = 0;
+static int dump_inserts = 0;
+static int column_inserts = 0;
+static int if_exists = 0;
+static int no_security_labels = 0;
+static int no_synchronized_snapshots = 0;
+static int no_unlogged_table_data = 0;
+static int serializable_deferrable = 0;
+
+
+static void help(const char *progname);
+static void setup_connection(Archive *AH, const char *dumpencoding,
+ const char *dumpsnapshot, char *use_role);
+static ArchiveFormat parseArchiveFormat(const char *format, ArchiveMode *mode);
+static void expand_schema_name_patterns(Archive *fout,
+ SimpleStringList *patterns,
+ SimpleOidList *oids);
+static void expand_table_name_patterns(Archive *fout,
+ SimpleStringList *patterns,
+ SimpleOidList *oids);
+static NamespaceInfo *findNamespace(Archive *fout, Oid nsoid, Oid objoid);
+static void dumpTableData(Archive *fout, TableDataInfo *tdinfo);
+static void refreshMatViewData(Archive *fout, TableDataInfo *tdinfo);
+static void guessConstraintInheritance(TableInfo *tblinfo, int numTables);
+static void dumpComment(Archive *fout, const char *target,
+ const char *namespace, const char *owner,
+ CatalogId catalogId, int subid, DumpId dumpId);
+static int findComments(Archive *fout, Oid classoid, Oid objoid,
+ CommentItem **items);
+static int collectComments(Archive *fout, CommentItem **items);
+static void dumpSecLabel(Archive *fout, const char *target,
+ 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, DumpableObject *dobj);
+static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo);
+static void dumpExtension(Archive *fout, ExtensionInfo *extinfo);
+static void dumpType(Archive *fout, TypeInfo *tyinfo);
+static void dumpBaseType(Archive *fout, TypeInfo *tyinfo);
+static void dumpEnumType(Archive *fout, TypeInfo *tyinfo);
+static void dumpRangeType(Archive *fout, TypeInfo *tyinfo);
+static void dumpUndefinedType(Archive *fout, TypeInfo *tyinfo);
+static void dumpDomain(Archive *fout, TypeInfo *tyinfo);
+static void dumpCompositeType(Archive *fout, TypeInfo *tyinfo);
+static void dumpCompositeTypeColComments(Archive *fout, TypeInfo *tyinfo);
+static void dumpShellType(Archive *fout, ShellTypeInfo *stinfo);
+static void dumpProcLang(Archive *fout, ProcLangInfo *plang);
+static void dumpFunc(Archive *fout, FuncInfo *finfo);
+static void dumpCast(Archive *fout, CastInfo *cast);
+static void dumpOpr(Archive *fout, OprInfo *oprinfo);
+static void dumpOpclass(Archive *fout, OpclassInfo *opcinfo);
+static void dumpOpfamily(Archive *fout, OpfamilyInfo *opfinfo);
+static void dumpCollation(Archive *fout, CollInfo *convinfo);
+static void dumpConversion(Archive *fout, ConvInfo *convinfo);
+static void dumpRule(Archive *fout, RuleInfo *rinfo);
+static void dumpAgg(Archive *fout, AggInfo *agginfo);
+static void dumpTrigger(Archive *fout, TriggerInfo *tginfo);
+static void dumpEventTrigger(Archive *fout, EventTriggerInfo *evtinfo);
+static void dumpTable(Archive *fout, TableInfo *tbinfo);
+static void dumpTableSchema(Archive *fout, TableInfo *tbinfo);
+static void dumpAttrDef(Archive *fout, AttrDefInfo *adinfo);
+static void dumpSequence(Archive *fout, TableInfo *tbinfo);
+static void dumpSequenceData(Archive *fout, TableDataInfo *tdinfo);
+static void dumpIndex(Archive *fout, IndxInfo *indxinfo);
+static void dumpConstraint(Archive *fout, ConstraintInfo *coninfo);
+static void dumpTableConstraintComment(Archive *fout, ConstraintInfo *coninfo);
+static void dumpTSParser(Archive *fout, TSParserInfo *prsinfo);
+static void dumpTSDictionary(Archive *fout, TSDictInfo *dictinfo);
+static void dumpTSTemplate(Archive *fout, TSTemplateInfo *tmplinfo);
+static void dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo);
+static void dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo);
+static void dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo);
+static void dumpUserMappings(Archive *fout,
+ const char *servername, const char *namespace,
+ const char *owner, CatalogId catalogId, DumpId dumpId);
+static void dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo);
+
+static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
+ const char *type, const char *name, const char *subname,
+ const char *tag, const char *nspname, const char *owner,
+ const char *acls);
+
+static void getDependencies(Archive *fout);
+static void BuildArchiveDependencies(Archive *fout);
+static void findDumpableDependencies(ArchiveHandle *AH, DumpableObject *dobj,
+ DumpId **dependencies, int *nDeps, int *allocDeps);
+
+static DumpableObject *createBoundaryObjects(void);
+static void addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
+ DumpableObject *boundaryObjs);
+
+static void getDomainConstraints(Archive *fout, TypeInfo *tyinfo);
+static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
+static void makeTableDataInfo(TableInfo *tbinfo, bool oids);
+static void buildMatViewRefreshDependencies(Archive *fout);
+static void getTableDataFKConstraints(void);
+static char *format_function_arguments(FuncInfo *finfo, char *funcargs,
+ bool is_agg);
+static char *format_function_arguments_old(Archive *fout,
+ FuncInfo *finfo, int nallargs,
+ char **allargtypes,
+ char **argmodes,
+ char **argnames);
+static char *format_function_signature(Archive *fout,
+ FuncInfo *finfo, bool honor_quotes);
+static char *convertRegProcReference(Archive *fout,
+ const char *proc);
+static char *convertOperatorReference(Archive *fout, const char *opr);
+static const char *convertTSFunction(Archive *fout, Oid funcOid);
+static Oid findLastBuiltinOid_V71(Archive *fout, const char *);
+static Oid findLastBuiltinOid_V70(Archive *fout);
+static void selectSourceSchema(Archive *fout, const char *schemaName);
+static char *getFormattedTypeName(Archive *fout, Oid oid, OidOptions opts);
+static char *myFormatType(const char *typname, int32 typmod);
+static void getBlobs(Archive *fout);
+static void dumpBlob(Archive *fout, BlobInfo *binfo);
+static int dumpBlobs(Archive *fout, void *arg);
+static void dumpDatabase(Archive *AH);
+static void dumpEncoding(Archive *AH);
+static void dumpStdStrings(Archive *AH);
+static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
+ PQExpBuffer upgrade_buffer, Oid pg_type_oid);
+static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
+ PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
+static void binary_upgrade_set_pg_class_oids(Archive *fout,
+ PQExpBuffer upgrade_buffer,
+ Oid pg_class_oid, bool is_index);
+static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
+ DumpableObject *dobj,
+ const char *objlabel);
+static const char *getAttrName(int attrnum, TableInfo *tblInfo);
+static const char *fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer);
+static char *get_synchronized_snapshot(Archive *fout);
+static PGresult *ExecuteSqlQueryForSingleRow(Archive *fout, char *query);
+static void setupDumpWorker(Archive *AHX, RestoreOptions *ropt);
+
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ const char *filename = NULL;
+ const char *format = "p";
+ const char *dbname = NULL;
+ const char *pghost = NULL;
+ const char *pgport = NULL;
+ const char *username = NULL;
+ const char *dumpencoding = NULL;
+ const char *dumpsnapshot = NULL;
+ bool oids = false;
+ TableInfo *tblinfo;
+ int numTables;
+ DumpableObject **dobjs;
+ int numObjs;
+ DumpableObject *boundaryObjs;
+ int i;
+ int numWorkers = 1;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ int compressLevel = -1;
+ int plainText = 0;
+ int outputClean = 0;
+ int outputCreateDB = 0;
+ bool outputBlobs = false;
+ int outputNoOwner = 0;
+ char *outputSuperuser = NULL;
+ char *use_role = NULL;
+ int optindex;
+ RestoreOptions *ropt;
+ ArchiveFormat archiveFormat = archUnknown;
+ ArchiveMode archiveMode;
+ Archive *fout; /* the script file */
+
+ static int disable_triggers = 0;
+ static int outputNoTablespaces = 0;
+ static int use_setsessauth = 0;
+
+ static struct option long_options[] = {
+ {"data-only", no_argument, NULL, 'a'},
+ {"blobs", no_argument, NULL, 'b'},
+ {"clean", no_argument, NULL, 'c'},
+ {"create", no_argument, NULL, 'C'},
+ {"dbname", required_argument, NULL, 'd'},
+ {"file", required_argument, NULL, 'f'},
+ {"format", required_argument, NULL, 'F'},
+ {"host", required_argument, NULL, 'h'},
+ {"ignore-version", no_argument, NULL, 'i'},
+ {"jobs", 1, NULL, 'j'},
+ {"no-reconnect", no_argument, NULL, 'R'},
+ {"oids", no_argument, NULL, 'o'},
+ {"no-owner", no_argument, NULL, 'O'},
+ {"port", required_argument, NULL, 'p'},
+ {"schema", required_argument, NULL, 'n'},
+ {"exclude-schema", required_argument, NULL, 'N'},
+ {"schema-only", no_argument, NULL, 's'},
+ {"superuser", required_argument, NULL, 'S'},
+ {"table", required_argument, NULL, 't'},
+ {"exclude-table", required_argument, NULL, 'T'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"username", required_argument, NULL, 'U'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"no-privileges", no_argument, NULL, 'x'},
+ {"no-acl", no_argument, NULL, 'x'},
+ {"compress", required_argument, NULL, 'Z'},
+ {"encoding", required_argument, NULL, 'E'},
+ {"help", no_argument, NULL, '?'},
+ {"version", no_argument, NULL, 'V'},
+
+ /*
+ * the following options don't have an equivalent short option letter
+ */
+ {"attribute-inserts", no_argument, &column_inserts, 1},
+ {"binary-upgrade", no_argument, &binary_upgrade, 1},
+ {"column-inserts", no_argument, &column_inserts, 1},
+ {"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1},
+ {"disable-triggers", no_argument, &disable_triggers, 1},
+ {"exclude-table-data", required_argument, NULL, 4},
+ {"if-exists", no_argument, &if_exists, 1},
+ {"inserts", no_argument, &dump_inserts, 1},
+ {"lock-wait-timeout", required_argument, NULL, 2},
+ {"no-tablespaces", no_argument, &outputNoTablespaces, 1},
+ {"quote-all-identifiers", no_argument, &quote_all_identifiers, 1},
+ {"role", required_argument, NULL, 3},
+ {"section", required_argument, NULL, 5},
+ {"serializable-deferrable", no_argument, &serializable_deferrable, 1},
+ {"snapshot", required_argument, NULL, 6},
+ {"use-set-session-authorization", no_argument, &use_setsessauth, 1},
+ {"no-security-labels", no_argument, &no_security_labels, 1},
+ {"no-synchronized-snapshots", no_argument, &no_synchronized_snapshots, 1},
+ {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
+
+ {NULL, 0, NULL, 0}
+ };
+
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_dump"));
+
+ /*
+ * Initialize what we need for parallel execution, especially for thread
+ * support on Windows.
+ */
+ init_parallel_dump_utils();
+
+ g_verbose = false;
+
+ strcpy(g_comment_start, "-- ");
+ g_comment_end[0] = '\0';
+ strcpy(g_opaque_type, "opaque");
+
+ dataOnly = schemaOnly = false;
+ dumpSections = DUMP_UNSECTIONED;
+ lockWaitTimeout = NULL;
+
+ progname = get_progname(argv[0]);
+
+ /* Set default options based on progname */
+ if (strcmp(progname, "pg_backup") == 0)
+ format = "c";
+
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+ {
+ help(progname);
+ exit_nicely(0);
+ }
+ if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+ {
+ puts("pg_dump (PostgreSQL) " PG_VERSION);
+ exit_nicely(0);
+ }
+ }
+
+ while ((c = getopt_long(argc, argv, "abcCd:E:f:F:h:ij:n:N:oOp:RsS:t:T:U:vwWxZ:",
+ long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'a': /* Dump data only */
+ dataOnly = true;
+ break;
+
+ case 'b': /* Dump blobs */
+ outputBlobs = true;
+ break;
+
+ case 'c': /* clean (i.e., drop) schema prior to create */
+ outputClean = 1;
+ break;
+
+ case 'C': /* Create DB */
+ outputCreateDB = 1;
+ break;
+
+ case 'd': /* database name */
+ dbname = pg_strdup(optarg);
+ break;
+
+ case 'E': /* Dump encoding */
+ dumpencoding = pg_strdup(optarg);
+ break;
+
+ case 'f':
+ filename = pg_strdup(optarg);
+ break;
+
+ case 'F':
+ format = pg_strdup(optarg);
+ break;
+
+ case 'h': /* server host */
+ pghost = pg_strdup(optarg);
+ break;
+
+ case 'i':
+ /* ignored, deprecated option */
+ break;
+
+ case 'j': /* number of dump jobs */
+ numWorkers = atoi(optarg);
+ break;
+
+ case 'n': /* include schema(s) */
+ simple_string_list_append(&schema_include_patterns, optarg);
+ include_everything = false;
+ break;
+
+ case 'N': /* exclude schema(s) */
+ simple_string_list_append(&schema_exclude_patterns, optarg);
+ break;
+
+ case 'o': /* Dump oids */
+ oids = true;
+ break;
+
+ case 'O': /* Don't reconnect to match owner */
+ outputNoOwner = 1;
+ break;
+
+ case 'p': /* server port */
+ pgport = pg_strdup(optarg);
+ break;
+
+ case 'R':
+ /* no-op, still accepted for backwards compatibility */
+ break;
+
+ case 's': /* dump schema only */
+ schemaOnly = true;
+ break;
+
+ case 'S': /* Username for superuser in plain text output */
+ outputSuperuser = pg_strdup(optarg);
+ break;
+
+ case 't': /* include table(s) */
+ simple_string_list_append(&table_include_patterns, optarg);
+ include_everything = false;
+ break;
+
+ case 'T': /* exclude table(s) */
+ simple_string_list_append(&table_exclude_patterns, optarg);
+ break;
+
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+
+ case 'v': /* verbose */
+ g_verbose = true;
+ break;
+
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+
+ case 'x': /* skip ACL dump */
+ aclsSkip = true;
+ break;
+
+ case 'Z': /* Compression Level */
+ compressLevel = atoi(optarg);
+ if (compressLevel < 0 || compressLevel > 9)
+ {
+ write_msg(NULL, "compression level must be in range 0..9\n");
+ exit_nicely(1);
+ }
+ break;
+
+ case 0:
+ /* This covers the long options. */
+ break;
+
+ case 2: /* lock-wait-timeout */
+ lockWaitTimeout = pg_strdup(optarg);
+ break;
+
+ case 3: /* SET ROLE */
+ use_role = pg_strdup(optarg);
+ break;
+
+ case 4: /* exclude table(s) data */
+ simple_string_list_append(&tabledata_exclude_patterns, optarg);
+ break;
+
+ case 5: /* section */
+ set_dump_section(optarg, &dumpSections);
+ break;
+
+ case 6: /* snapshot */
+ dumpsnapshot = pg_strdup(optarg);
+ break;
+
+ default:
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+ exit_nicely(1);
+ }
+ }
+
+ /*
+ * Non-option argument specifies database name as long as it wasn't
+ * already specified with -d / --dbname
+ */
+ if (optind < argc && dbname == NULL)
+ dbname = argv[optind++];
+
+ /* Complain if any arguments remain */
+ if (optind < argc)
+ {
+ fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
+ progname, argv[optind]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit_nicely(1);
+ }
+
+ /* --column-inserts implies --inserts */
+ if (column_inserts)
+ dump_inserts = 1;
+
+ if (dataOnly && schemaOnly)
+ {
+ write_msg(NULL, "options -s/--schema-only and -a/--data-only cannot be used together\n");
+ exit_nicely(1);
+ }
+
+ if (dataOnly && outputClean)
+ {
+ write_msg(NULL, "options -c/--clean and -a/--data-only cannot be used together\n");
+ exit_nicely(1);
+ }
+
+ if (dump_inserts && oids)
+ {
+ write_msg(NULL, "options --inserts/--column-inserts and -o/--oids cannot be used together\n");
+ write_msg(NULL, "(The INSERT command cannot set OIDs.)\n");
+ exit_nicely(1);
+ }
+
+ if (if_exists && !outputClean)
+ exit_horribly(NULL, "option --if-exists requires option -c/--clean\n");
+
+ /* Identify archive format to emit */
+ archiveFormat = parseArchiveFormat(format, &archiveMode);
+
+ /* archiveFormat specific setup */
+ if (archiveFormat == archNull)
+ plainText = 1;
+
+ /* Custom and directory formats are compressed by default, others not */
+ if (compressLevel == -1)
+ {
+ if (archiveFormat == archCustom || archiveFormat == archDirectory)
+ compressLevel = Z_DEFAULT_COMPRESSION;
+ else
+ compressLevel = 0;
+ }
+
+ /*
+ * On Windows we can only have at most MAXIMUM_WAIT_OBJECTS (= 64 usually)
+ * parallel jobs because that's the maximum limit for the
+ * WaitForMultipleObjects() call.
+ */
+ if (numWorkers <= 0
+#ifdef WIN32
+ || numWorkers > MAXIMUM_WAIT_OBJECTS
+#endif
+ )
+ exit_horribly(NULL, "%s: invalid number of parallel jobs\n", progname);
+
+ /* Parallel backup only in the directory archive format so far */
+ if (archiveFormat != archDirectory && numWorkers > 1)
+ exit_horribly(NULL, "parallel backup only supported by the directory format\n");
+
+ /* Open the output file */
+ fout = CreateArchive(filename, archiveFormat, compressLevel, archiveMode,
+ setupDumpWorker);
+
+ /* Register the cleanup hook */
+ on_exit_close_archive(fout);
+
+ if (fout == NULL)
+ exit_horribly(NULL, "could not open output file \"%s\" for writing\n", filename);
+
+ /* Let the archiver know how noisy to be */
+ fout->verbose = g_verbose;
+
+ /*
+ * We allow the server to be back to 7.0, and up to any minor release of
+ * our own major version. (See also version check in pg_dumpall.c.)
+ */
+ fout->minRemoteVersion = 70000;
+ fout->maxRemoteVersion = (PG_VERSION_NUM / 100) * 100 + 99;
+
+ fout->numWorkers = numWorkers;
+
+ /*
+ * Open the database using the Archiver, so it knows about it. Errors mean
+ * death.
+ */
+ ConnectDatabase(fout, dbname, pghost, pgport, username, prompt_password);
+ setup_connection(fout, dumpencoding, dumpsnapshot, use_role);
+
+ /*
+ * Disable security label support if server version < v9.1.x (prevents
+ * access to nonexistent pg_seclabel catalog)
+ */
+ if (fout->remoteVersion < 90100)
+ no_security_labels = 1;
+
+ /*
+ * When running against 9.0 or later, check if we are in recovery mode,
+ * which means we are on a hot standby.
+ */
+ if (fout->remoteVersion >= 90000)
+ {
+ PGresult *res = ExecuteSqlQueryForSingleRow(fout, "SELECT pg_catalog.pg_is_in_recovery()");
+
+ if (strcmp(PQgetvalue(res, 0, 0), "t") == 0)
+ {
+ /*
+ * On hot standby slaves, never try to dump unlogged table data,
+ * since it will just throw an error.
+ */
+ no_unlogged_table_data = true;
+ }
+ PQclear(res);
+ }
+
+ /* Select the appropriate subquery to convert user IDs to names */
+ if (fout->remoteVersion >= 80100)
+ username_subquery = "SELECT rolname FROM pg_catalog.pg_roles WHERE oid =";
+ else if (fout->remoteVersion >= 70300)
+ username_subquery = "SELECT usename FROM pg_catalog.pg_user WHERE usesysid =";
+ else
+ username_subquery = "SELECT usename FROM pg_user WHERE usesysid =";
+
+ /* check the version for the synchronized snapshots feature */
+ if (numWorkers > 1 && fout->remoteVersion < 90200
+ && !no_synchronized_snapshots)
+ exit_horribly(NULL,
+ "Synchronized snapshots are not supported by this server version.\n"
+ "Run with --no-synchronized-snapshots instead if you do not need\n"
+ "synchronized snapshots.\n");
+
+ /* check the version when a snapshot is explicitly specified by user */
+ if (dumpsnapshot && fout->remoteVersion < 90200)
+ exit_horribly(NULL,
+ "Exported snapshots are not supported by this server version.\n");
+
+ /* Find the last built-in OID, if needed */
+ if (fout->remoteVersion < 70300)
+ {
+ if (fout->remoteVersion >= 70100)
+ g_last_builtin_oid = findLastBuiltinOid_V71(fout,
+ PQdb(GetConnection(fout)));
+ else
+ g_last_builtin_oid = findLastBuiltinOid_V70(fout);
+ if (g_verbose)
+ write_msg(NULL, "last built-in OID is %u\n", g_last_builtin_oid);
+ }
+
+ /* Expand schema selection patterns into OID lists */
+ if (schema_include_patterns.head != NULL)
+ {
+ expand_schema_name_patterns(fout, &schema_include_patterns,
+ &schema_include_oids);
+ if (schema_include_oids.head == NULL)
+ exit_horribly(NULL, "No matching schemas were found\n");
+ }
+ expand_schema_name_patterns(fout, &schema_exclude_patterns,
+ &schema_exclude_oids);
+ /* non-matching exclusion patterns aren't an error */
+
+ /* Expand table selection patterns into OID lists */
+ if (table_include_patterns.head != NULL)
+ {
+ expand_table_name_patterns(fout, &table_include_patterns,
+ &table_include_oids);
+ if (table_include_oids.head == NULL)
+ exit_horribly(NULL, "No matching tables were found\n");
+ }
+ expand_table_name_patterns(fout, &table_exclude_patterns,
+ &table_exclude_oids);
+
+ expand_table_name_patterns(fout, &tabledata_exclude_patterns,
+ &tabledata_exclude_oids);
+
+ /* non-matching exclusion patterns aren't an error */
+
+ /*
+ * Dumping blobs is now default unless we saw an inclusion switch or -s
+ * ... but even if we did see one of these, -b turns it back on.
+ */
+ if (include_everything && !schemaOnly)
+ outputBlobs = true;
+
+ /*
+ * Now scan the database and create DumpableObject structs for all the
+ * objects we intend to dump.
+ */
+ tblinfo = getSchemaData(fout, &numTables);
+
+ if (fout->remoteVersion < 80400)
+ guessConstraintInheritance(tblinfo, numTables);
+
+ if (!schemaOnly)
+ {
+ getTableData(tblinfo, numTables, oids);
+ buildMatViewRefreshDependencies(fout);
+ if (dataOnly)
+ getTableDataFKConstraints();
+ }
+
+ if (outputBlobs)
+ getBlobs(fout);
+
+ /*
+ * Collect dependency data to assist in ordering the objects.
+ */
+ getDependencies(fout);
+
+ /* Lastly, create dummy objects to represent the section boundaries */
+ boundaryObjs = createBoundaryObjects();
+
+ /* Get pointers to all the known DumpableObjects */
+ getDumpableObjects(&dobjs, &numObjs);
+
+ /*
+ * Add dummy dependencies to enforce the dump section ordering.
+ */
+ addBoundaryDependencies(dobjs, numObjs, boundaryObjs);
+
+ /*
+ * Sort the objects into a safe dump order (no forward references).
+ *
+ * In 7.3 or later, we can rely on dependency information to help us
+ * determine a safe order, so the initial sort is mostly for cosmetic
+ * purposes: we sort by name to ensure that logically identical schemas
+ * will dump identically. Before 7.3 we don't have dependencies and we
+ * use OID ordering as an (unreliable) guide to creation order.
+ */
+ if (fout->remoteVersion >= 70300)
+ sortDumpableObjectsByTypeName(dobjs, numObjs);
+ else
+ sortDumpableObjectsByTypeOid(dobjs, numObjs);
+
+ /* If we do a parallel dump, we want the largest tables to go first */
+ if (archiveFormat == archDirectory && numWorkers > 1)
+ sortDataAndIndexObjectsBySize(dobjs, numObjs);
+
+ sortDumpableObjects(dobjs, numObjs,
+ boundaryObjs[0].dumpId, boundaryObjs[1].dumpId);
+
+ /*
+ * Create archive TOC entries for all the objects to be dumped, in a safe
+ * order.
+ */
+
+ /* First the special ENCODING and STDSTRINGS entries. */
+ dumpEncoding(fout);
+ dumpStdStrings(fout);
+
+ /* The database item is always next, unless we don't want it at all */
+ if (include_everything && !dataOnly)
+ dumpDatabase(fout);
+
+ /* Now the rearrangeable objects. */
+ for (i = 0; i < numObjs; i++)
+ dumpDumpableObject(fout, dobjs[i]);
+
+ /*
+ * Set up options info to ensure we dump what we want.
+ */
+ ropt = NewRestoreOptions();
+ ropt->filename = filename;
+ ropt->dropSchema = outputClean;
+ ropt->dataOnly = dataOnly;
+ ropt->schemaOnly = schemaOnly;
+ ropt->if_exists = if_exists;
+ ropt->dumpSections = dumpSections;
+ ropt->aclsSkip = aclsSkip;
+ ropt->superuser = outputSuperuser;
+ ropt->createDB = outputCreateDB;
+ ropt->noOwner = outputNoOwner;
+ ropt->noTablespace = outputNoTablespaces;
+ ropt->disable_triggers = disable_triggers;
+ ropt->use_setsessauth = use_setsessauth;
+
+ if (compressLevel == -1)
+ ropt->compression = 0;
+ else
+ ropt->compression = compressLevel;
+
+ ropt->suppressDumpWarnings = true; /* We've already shown them */
+
+ SetArchiveRestoreOptions(fout, ropt);
+
+ /*
+ * The archive's TOC entries are now marked as to which ones will actually
+ * be output, so we can set up their dependency lists properly. This isn't
+ * necessary for plain-text output, though.
+ */
+ if (!plainText)
+ BuildArchiveDependencies(fout);
+
+ /*
+ * And finally we can do the actual output.
+ *
+ * Note: for non-plain-text output formats, the output file is written
+ * inside CloseArchive(). This is, um, bizarre; but not worth changing
+ * right now.
+ */
+ if (plainText)
+ RestoreArchive(fout);
+
+ CloseArchive(fout);
+
+ exit_nicely(0);
+}
+
+
+static void
+help(const char *progname)
+{
+ printf(_("%s dumps a database as a text file or to other formats.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+
+ printf(_("\nGeneral options:\n"));
+ printf(_(" -f, --file=FILENAME output file or directory name\n"));
+ printf(_(" -F, --format=c|d|t|p output file format (custom, directory, tar,\n"
+ " plain text (default))\n"));
+ printf(_(" -j, --jobs=NUM use this many parallel jobs to dump\n"));
+ printf(_(" -v, --verbose verbose mode\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -Z, --compress=0-9 compression level for compressed formats\n"));
+ printf(_(" --lock-wait-timeout=TIMEOUT fail after waiting TIMEOUT for a table lock\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+
+ printf(_("\nOptions controlling the output content:\n"));
+ printf(_(" -a, --data-only dump only the data, not the schema\n"));
+ printf(_(" -b, --blobs include large objects in dump\n"));
+ printf(_(" -c, --clean clean (drop) database objects before recreating\n"));
+ printf(_(" -C, --create include commands to create database in dump\n"));
+ printf(_(" -E, --encoding=ENCODING dump the data in encoding ENCODING\n"));
+ printf(_(" -n, --schema=SCHEMA dump the named schema(s) only\n"));
+ printf(_(" -N, --exclude-schema=SCHEMA do NOT dump the named schema(s)\n"));
+ printf(_(" -o, --oids include OIDs in dump\n"));
+ printf(_(" -O, --no-owner skip restoration of object ownership in\n"
+ " plain-text format\n"));
+ printf(_(" -s, --schema-only dump only the schema, no data\n"));
+ printf(_(" -S, --superuser=NAME superuser user name to use in plain-text format\n"));
+ printf(_(" -t, --table=TABLE dump the named table(s) only\n"));
+ printf(_(" -T, --exclude-table=TABLE do NOT dump the named table(s)\n"));
+ printf(_(" -x, --no-privileges do not dump privileges (grant/revoke)\n"));
+ printf(_(" --binary-upgrade for use by upgrade utilities only\n"));
+ printf(_(" --column-inserts dump data as INSERT commands with column names\n"));
+ printf(_(" --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n"));
+ printf(_(" --disable-triggers disable triggers during data-only restore\n"));
+ printf(_(" --exclude-table-data=TABLE do NOT dump data for the named table(s)\n"));
+ printf(_(" --if-exists use IF EXISTS when dropping objects\n"));
+ printf(_(" --inserts dump data as INSERT commands, rather than COPY\n"));
+ printf(_(" --no-security-labels do not dump security label assignments\n"));
+ printf(_(" --no-synchronized-snapshots do not use synchronized snapshots in parallel jobs\n"));
+ printf(_(" --no-tablespaces do not dump tablespace assignments\n"));
+ printf(_(" --no-unlogged-table-data do not dump unlogged table data\n"));
+ printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n"));
+ printf(_(" --section=SECTION dump named section (pre-data, data, or post-data)\n"));
+ printf(_(" --serializable-deferrable wait until the dump can run without anomalies\n"));
+ printf(_(" --snapshot=SNAPSHOT use given synchronous snapshot for the dump\n"));
+ printf(_(" --use-set-session-authorization\n"
+ " use SET SESSION AUTHORIZATION commands instead of\n"
+ " ALTER OWNER commands to set ownership\n"));
+
+ printf(_("\nConnection options:\n"));
+ printf(_(" -d, --dbname=DBNAME database to dump\n"));
+ printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
+ printf(_(" -p, --port=PORT database server port number\n"));
+ printf(_(" -U, --username=NAME connect as specified database user\n"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt (should happen automatically)\n"));
+ printf(_(" --role=ROLENAME do SET ROLE before dump\n"));
+
+ printf(_("\nIf no database name is supplied, then the PGDATABASE environment\n"
+ "variable value is used.\n\n"));
+ printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n"));
+}
+
+static void
+setup_connection(Archive *AH, const char *dumpencoding,
+ const char *dumpsnapshot, char *use_role)
+{
+ PGconn *conn = GetConnection(AH);
+ const char *std_strings;
+
+ /*
+ * Set the client encoding if requested. If dumpencoding == NULL then
+ * either it hasn't been requested or we're a cloned connection and then
+ * this has already been set in CloneArchive according to the original
+ * connection encoding.
+ */
+ if (dumpencoding)
+ {
+ if (PQsetClientEncoding(conn, dumpencoding) < 0)
+ exit_horribly(NULL, "invalid client encoding \"%s\" specified\n",
+ dumpencoding);
+ }
+
+ /*
+ * Get the active encoding and the standard_conforming_strings setting, so
+ * we know how to escape strings.
+ */
+ AH->encoding = PQclientEncoding(conn);
+
+ std_strings = PQparameterStatus(conn, "standard_conforming_strings");
+ AH->std_strings = (std_strings && strcmp(std_strings, "on") == 0);
+
+ /* Set the role if requested */
+ if (!use_role && AH->use_role)
+ use_role = AH->use_role;
+
+ /* Set the role if requested */
+ if (use_role && AH->remoteVersion >= 80100)
+ {
+ PQExpBuffer query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "SET ROLE %s", fmtId(use_role));
+ ExecuteSqlStatement(AH, query->data);
+ destroyPQExpBuffer(query);
+
+ /* save this for later use on parallel connections */
+ if (!AH->use_role)
+ AH->use_role = strdup(use_role);
+ }
+
+ /* Set the datestyle to ISO to ensure the dump's portability */
+ ExecuteSqlStatement(AH, "SET DATESTYLE = ISO");
+
+ /* Likewise, avoid using sql_standard intervalstyle */
+ if (AH->remoteVersion >= 80400)
+ ExecuteSqlStatement(AH, "SET INTERVALSTYLE = POSTGRES");
+
+ /*
+ * If supported, set extra_float_digits so that we can dump float data
+ * exactly (given correctly implemented float I/O code, anyway)
+ */
+ if (AH->remoteVersion >= 90000)
+ ExecuteSqlStatement(AH, "SET extra_float_digits TO 3");
+ else if (AH->remoteVersion >= 70400)
+ ExecuteSqlStatement(AH, "SET extra_float_digits TO 2");
+
+ /*
+ * If synchronized scanning is supported, disable it, to prevent
+ * unpredictable changes in row ordering across a dump and reload.
+ */
+ if (AH->remoteVersion >= 80300)
+ ExecuteSqlStatement(AH, "SET synchronize_seqscans TO off");
+
+ /*
+ * Disable timeouts if supported.
+ */
+ if (AH->remoteVersion >= 70300)
+ ExecuteSqlStatement(AH, "SET statement_timeout = 0");
+ if (AH->remoteVersion >= 90300)
+ ExecuteSqlStatement(AH, "SET lock_timeout = 0");
+
+ /*
+ * Quote all identifiers, if requested.
+ */
+ if (quote_all_identifiers && AH->remoteVersion >= 90100)
+ ExecuteSqlStatement(AH, "SET quote_all_identifiers = true");
+
+ /*
+ * Start transaction-snapshot mode transaction to dump consistent data.
+ */
+ ExecuteSqlStatement(AH, "BEGIN");
+ if (AH->remoteVersion >= 90100)
+ {
+ /*
+ * To support the combination of serializable_deferrable with the jobs
+ * option we use REPEATABLE READ for the worker connections that are
+ * passed a snapshot. As long as the snapshot is acquired in a
+ * SERIALIZABLE, READ ONLY, DEFERRABLE transaction, its use within a
+ * REPEATABLE READ transaction provides the appropriate integrity
+ * guarantees. This is a kluge, but safe for back-patching.
+ */
+ if (serializable_deferrable && AH->sync_snapshot_id == NULL)
+ ExecuteSqlStatement(AH,
+ "SET TRANSACTION ISOLATION LEVEL "
+ "SERIALIZABLE, READ ONLY, DEFERRABLE");
+ else
+ ExecuteSqlStatement(AH,
+ "SET TRANSACTION ISOLATION LEVEL "
+ "REPEATABLE READ, READ ONLY");
+ }
+ else if (AH->remoteVersion >= 70400)
+ {
+ /* note: comma was not accepted in SET TRANSACTION before 8.0 */
+ ExecuteSqlStatement(AH,
+ "SET TRANSACTION ISOLATION LEVEL "
+ "SERIALIZABLE READ ONLY");
+ }
+ else
+ ExecuteSqlStatement(AH,
+ "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
+
+
+ /*
+ * define an export snapshot, either chosen by user or needed for
+ * parallel dump.
+ */
+ if (dumpsnapshot)
+ AH->sync_snapshot_id = strdup(dumpsnapshot);
+
+
+ if (AH->sync_snapshot_id)
+ {
+ PQExpBuffer query = createPQExpBuffer();
+ appendPQExpBuffer(query, "SET TRANSACTION SNAPSHOT ");
+ appendStringLiteralConn(query, AH->sync_snapshot_id, conn);
+ ExecuteSqlStatement(AH, query->data);
+ destroyPQExpBuffer(query);
+ }
+ else if (AH->numWorkers > 1 &&
+ AH->remoteVersion >= 90200 &&
+ !no_synchronized_snapshots)
+ AH->sync_snapshot_id = get_synchronized_snapshot(AH);
+}
+
+static void
+setupDumpWorker(Archive *AHX, RestoreOptions *ropt)
+{
+ setup_connection(AHX, NULL, NULL, NULL);
+}
+
+static char *
+get_synchronized_snapshot(Archive *fout)
+{
+ char *query = "SELECT pg_export_snapshot()";
+ char *result;
+ PGresult *res;
+
+ res = ExecuteSqlQueryForSingleRow(fout, query);
+ result = strdup(PQgetvalue(res, 0, 0));
+ PQclear(res);
+
+ return result;
+}
+
+static ArchiveFormat
+parseArchiveFormat(const char *format, ArchiveMode *mode)
+{
+ ArchiveFormat archiveFormat;
+
+ *mode = archModeWrite;
+
+ if (pg_strcasecmp(format, "a") == 0 || pg_strcasecmp(format, "append") == 0)
+ {
+ /* This is used by pg_dumpall, and is not documented */
+ archiveFormat = archNull;
+ *mode = archModeAppend;
+ }
+ else if (pg_strcasecmp(format, "c") == 0)
+ archiveFormat = archCustom;
+ else if (pg_strcasecmp(format, "custom") == 0)
+ archiveFormat = archCustom;
+ else if (pg_strcasecmp(format, "d") == 0)
+ archiveFormat = archDirectory;
+ else if (pg_strcasecmp(format, "directory") == 0)
+ archiveFormat = archDirectory;
+ else if (pg_strcasecmp(format, "p") == 0)
+ archiveFormat = archNull;
+ else if (pg_strcasecmp(format, "plain") == 0)
+ archiveFormat = archNull;
+ else if (pg_strcasecmp(format, "t") == 0)
+ archiveFormat = archTar;
+ else if (pg_strcasecmp(format, "tar") == 0)
+ archiveFormat = archTar;
+ else
+ exit_horribly(NULL, "invalid output format \"%s\" specified\n", format);
+ return archiveFormat;
+}
+
+/*
+ * Find the OIDs of all schemas matching the given list of patterns,
+ * and append them to the given OID list.
+ */
+static void
+expand_schema_name_patterns(Archive *fout,
+ SimpleStringList *patterns,
+ SimpleOidList *oids)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ SimpleStringListCell *cell;
+ int i;
+
+ if (patterns->head == NULL)
+ return; /* nothing to do */
+
+ if (fout->remoteVersion < 70300)
+ exit_horribly(NULL, "server version must be at least 7.3 to use schema selection switches\n");
+
+ query = createPQExpBuffer();
+
+ /*
+ * We use UNION ALL rather than UNION; this might sometimes result in
+ * duplicate entries in the OID list, but we don't care.
+ */
+
+ for (cell = patterns->head; cell; cell = cell->next)
+ {
+ if (cell != patterns->head)
+ appendPQExpBufferStr(query, "UNION ALL\n");
+ appendPQExpBuffer(query,
+ "SELECT oid FROM pg_catalog.pg_namespace n\n");
+ processSQLNamePattern(GetConnection(fout), query, cell->val, false,
+ false, NULL, "n.nspname", NULL, NULL);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ simple_oid_list_append(oids, atooid(PQgetvalue(res, i, 0)));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * Find the OIDs of all tables matching the given list of patterns,
+ * and append them to the given OID list.
+ */
+static void
+expand_table_name_patterns(Archive *fout,
+ SimpleStringList *patterns, SimpleOidList *oids)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ SimpleStringListCell *cell;
+ int i;
+
+ if (patterns->head == NULL)
+ return; /* nothing to do */
+
+ query = createPQExpBuffer();
+
+ /*
+ * We use UNION ALL rather than UNION; this might sometimes result in
+ * duplicate entries in the OID list, but we don't care.
+ */
+
+ for (cell = patterns->head; cell; cell = cell->next)
+ {
+ if (cell != patterns->head)
+ appendPQExpBufferStr(query, "UNION ALL\n");
+ appendPQExpBuffer(query,
+ "SELECT c.oid"
+ "\nFROM pg_catalog.pg_class c"
+ "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ processSQLNamePattern(GetConnection(fout), query, cell->val, true,
+ false, "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ simple_oid_list_append(oids, atooid(PQgetvalue(res, i, 0)));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * selectDumpableNamespace: policy-setting subroutine
+ * Mark a namespace as to be dumped or not
+ */
+static void
+selectDumpableNamespace(NamespaceInfo *nsinfo)
+{
+ /*
+ * If specific tables are being dumped, do not dump any complete
+ * namespaces. If specific namespaces are being dumped, dump just those
+ * namespaces. Otherwise, dump all non-system namespaces.
+ */
+ if (table_include_oids.head != NULL)
+ nsinfo->dobj.dump = false;
+ else if (schema_include_oids.head != NULL)
+ nsinfo->dobj.dump = simple_oid_list_member(&schema_include_oids,
+ nsinfo->dobj.catId.oid);
+ else if (strncmp(nsinfo->dobj.name, "pg_", 3) == 0 ||
+ strcmp(nsinfo->dobj.name, "information_schema") == 0)
+ nsinfo->dobj.dump = false;
+ else
+ nsinfo->dobj.dump = true;
+
+ /*
+ * In any case, a namespace can be excluded by an exclusion switch
+ */
+ if (nsinfo->dobj.dump &&
+ simple_oid_list_member(&schema_exclude_oids,
+ nsinfo->dobj.catId.oid))
+ nsinfo->dobj.dump = false;
+}
+
+/*
+ * selectDumpableTable: policy-setting subroutine
+ * Mark a table as to be dumped or not
+ */
+static void
+selectDumpableTable(TableInfo *tbinfo)
+{
+ /*
+ * If specific tables are being dumped, dump just those tables; else, dump
+ * according to the parent namespace's dump flag.
+ */
+ if (table_include_oids.head != NULL)
+ tbinfo->dobj.dump = simple_oid_list_member(&table_include_oids,
+ tbinfo->dobj.catId.oid);
+ else
+ tbinfo->dobj.dump = tbinfo->dobj.namespace->dobj.dump;
+
+ /*
+ * In any case, a table can be excluded by an exclusion switch
+ */
+ if (tbinfo->dobj.dump &&
+ simple_oid_list_member(&table_exclude_oids,
+ tbinfo->dobj.catId.oid))
+ tbinfo->dobj.dump = false;
+}
+
+/*
+ * selectDumpableType: policy-setting subroutine
+ * Mark a type as to be dumped or not
+ *
+ * If it's a table's rowtype or an autogenerated array type, we also apply a
+ * special type code to facilitate sorting into the desired order. (We don't
+ * want to consider those to be ordinary types because that would bring tables
+ * up into the datatype part of the dump order.) We still set the object's
+ * dump flag; that's not going to cause the dummy type to be dumped, but we
+ * need it so that casts involving such types will be dumped correctly -- see
+ * dumpCast. This means the flag should be set the same as for the underlying
+ * object (the table or base type).
+ */
+static void
+selectDumpableType(TypeInfo *tyinfo)
+{
+ /* skip complex types, except for standalone composite types */
+ if (OidIsValid(tyinfo->typrelid) &&
+ tyinfo->typrelkind != RELKIND_COMPOSITE_TYPE)
+ {
+ TableInfo *tytable = findTableByOid(tyinfo->typrelid);
+
+ tyinfo->dobj.objType = DO_DUMMY_TYPE;
+ if (tytable != NULL)
+ tyinfo->dobj.dump = tytable->dobj.dump;
+ else
+ tyinfo->dobj.dump = false;
+ return;
+ }
+
+ /* skip auto-generated array types */
+ if (tyinfo->isArray)
+ {
+ tyinfo->dobj.objType = DO_DUMMY_TYPE;
+
+ /*
+ * Fall through to set the dump flag; we assume that the subsequent
+ * rules will do the same thing as they would for the array's base
+ * type. (We cannot reliably look up the base type here, since
+ * getTypes may not have processed it yet.)
+ */
+ }
+
+ /* dump only types in dumpable namespaces */
+ if (!tyinfo->dobj.namespace->dobj.dump)
+ tyinfo->dobj.dump = false;
+ else
+ tyinfo->dobj.dump = true;
+}
+
+/*
+ * selectDumpableDefaultACL: policy-setting subroutine
+ * Mark a default ACL as to be dumped or not
+ *
+ * For per-schema default ACLs, dump if the schema is to be dumped.
+ * Otherwise dump if we are dumping "everything". Note that dataOnly
+ * and aclsSkip are checked separately.
+ */
+static void
+selectDumpableDefaultACL(DefaultACLInfo *dinfo)
+{
+ if (dinfo->dobj.namespace)
+ dinfo->dobj.dump = dinfo->dobj.namespace->dobj.dump;
+ else
+ dinfo->dobj.dump = include_everything;
+}
+
+/*
+ * selectDumpableCast: policy-setting subroutine
+ * Mark a cast as to be dumped or not
+ *
+ * Casts do not belong to any particular namespace (since they haven't got
+ * names), nor do they have identifiable owners. To distinguish user-defined
+ * casts from built-in ones, we must resort to checking whether the cast's
+ * OID is in the range reserved for initdb.
+ */
+static void
+selectDumpableCast(CastInfo *cast)
+{
+ if (cast->dobj.catId.oid < (Oid) FirstNormalObjectId)
+ cast->dobj.dump = false;
+ else
+ cast->dobj.dump = include_everything;
+}
+
+/*
+ * selectDumpableExtension: policy-setting subroutine
+ * Mark an extension as to be dumped or not
+ *
+ * Normally, we dump all extensions, or none of them if include_everything
+ * is false (i.e., a --schema or --table switch was given). However, in
+ * binary-upgrade mode it's necessary to skip built-in extensions, since we
+ * assume those will already be installed in the target database. We identify
+ * such extensions by their having OIDs in the range reserved for initdb.
+ */
+static void
+selectDumpableExtension(ExtensionInfo *extinfo)
+{
+ if (binary_upgrade && extinfo->dobj.catId.oid < (Oid) FirstNormalObjectId)
+ extinfo->dobj.dump = false;
+ else
+ extinfo->dobj.dump = include_everything;
+}
+
+/*
+ * selectDumpableObject: policy-setting subroutine
+ * Mark a generic dumpable object as to be dumped or not
+ *
+ * Use this only for object types without a special-case routine above.
+ */
+static void
+selectDumpableObject(DumpableObject *dobj)
+{
+ /*
+ * Default policy is to dump if parent namespace is dumpable, or always
+ * for non-namespace-associated items.
+ */
+ if (dobj->namespace)
+ dobj->dump = dobj->namespace->dobj.dump;
+ else
+ dobj->dump = true;
+}
+
+/*
+ * Dump a table's contents for loading using the COPY command
+ * - this routine is called by the Archiver when it wants the table
+ * to be dumped.
+ */
+
+static int
+dumpTableData_copy(Archive *fout, void *dcontext)
+{
+ TableDataInfo *tdinfo = (TableDataInfo *) dcontext;
+ TableInfo *tbinfo = tdinfo->tdtable;
+ const char *classname = tbinfo->dobj.name;
+ const bool hasoids = tbinfo->hasoids;
+ const bool oids = tdinfo->oids;
+ PQExpBuffer q = createPQExpBuffer();
+
+ /*
+ * Note: can't use getThreadLocalPQExpBuffer() here, we're calling fmtId
+ * which uses it already.
+ */
+ PQExpBuffer clistBuf = createPQExpBuffer();
+ PGconn *conn = GetConnection(fout);
+ PGresult *res;
+ int ret;
+ char *copybuf;
+ const char *column_list;
+
+ if (g_verbose)
+ write_msg(NULL, "dumping contents of table %s\n", classname);
+
+ /*
+ * Make sure we are in proper schema. We will qualify the table name
+ * below anyway (in case its name conflicts with a pg_catalog table); but
+ * this ensures reproducible results in case the table contains regproc,
+ * regclass, etc columns.
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ /*
+ * If possible, specify the column list explicitly so that we have no
+ * possibility of retrieving data in the wrong column order. (The default
+ * column ordering of COPY will not be what we want in certain corner
+ * cases involving ADD COLUMN and inheritance.)
+ */
+ if (fout->remoteVersion >= 70300)
+ column_list = fmtCopyColumnList(tbinfo, clistBuf);
+ else
+ column_list = ""; /* can't select columns in COPY */
+
+ if (oids && hasoids)
+ {
+ appendPQExpBuffer(q, "COPY %s %s WITH OIDS TO stdout;",
+ fmtQualifiedId(fout->remoteVersion,
+ tbinfo->dobj.namespace->dobj.name,
+ classname),
+ column_list);
+ }
+ else if (tdinfo->filtercond)
+ {
+ /* Note: this syntax is only supported in 8.2 and up */
+ appendPQExpBufferStr(q, "COPY (SELECT ");
+ /* klugery to get rid of parens in column list */
+ if (strlen(column_list) > 2)
+ {
+ appendPQExpBufferStr(q, column_list + 1);
+ q->data[q->len - 1] = ' ';
+ }
+ else
+ appendPQExpBufferStr(q, "* ");
+ appendPQExpBuffer(q, "FROM %s %s) TO stdout;",
+ fmtQualifiedId(fout->remoteVersion,
+ tbinfo->dobj.namespace->dobj.name,
+ classname),
+ tdinfo->filtercond);
+ }
+ else
+ {
+ appendPQExpBuffer(q, "COPY %s %s TO stdout;",
+ fmtQualifiedId(fout->remoteVersion,
+ tbinfo->dobj.namespace->dobj.name,
+ classname),
+ column_list);
+ }
+ res = ExecuteSqlQuery(fout, q->data, PGRES_COPY_OUT);
+ PQclear(res);
+ destroyPQExpBuffer(clistBuf);
+
+ for (;;)
+ {
+ ret = PQgetCopyData(conn, &copybuf, 0);
+
+ if (ret < 0)
+ break; /* done or error */
+
+ if (copybuf)
+ {
+ WriteData(fout, copybuf, ret);
+ PQfreemem(copybuf);
+ }
+
+ /* ----------
+ * THROTTLE:
+ *
+ * There was considerable discussion in late July, 2000 regarding
+ * slowing down pg_dump when backing up large tables. Users with both
+ * slow & fast (multi-processor) machines experienced performance
+ * degradation when doing a backup.
+ *
+ * Initial attempts based on sleeping for a number of ms for each ms
+ * of work were deemed too complex, then a simple 'sleep in each loop'
+ * implementation was suggested. The latter failed because the loop
+ * was too tight. Finally, the following was implemented:
+ *
+ * If throttle is non-zero, then
+ * See how long since the last sleep.
+ * Work out how long to sleep (based on ratio).
+ * If sleep is more than 100ms, then
+ * sleep
+ * reset timer
+ * EndIf
+ * EndIf
+ *
+ * where the throttle value was the number of ms to sleep per ms of
+ * work. The calculation was done in each loop.
+ *
+ * Most of the hard work is done in the backend, and this solution
+ * still did not work particularly well: on slow machines, the ratio
+ * was 50:1, and on medium paced machines, 1:1, and on fast
+ * multi-processor machines, it had little or no effect, for reasons
+ * that were unclear.
+ *
+ * Further discussion ensued, and the proposal was dropped.
+ *
+ * For those people who want this feature, it can be implemented using
+ * gettimeofday in each loop, calculating the time since last sleep,
+ * multiplying that by the sleep ratio, then if the result is more
+ * than a preset 'minimum sleep time' (say 100ms), call the 'select'
+ * function to sleep for a subsecond period ie.
+ *
+ * select(0, NULL, NULL, NULL, &tvi);
+ *
+ * This will return after the interval specified in the structure tvi.
+ * Finally, call gettimeofday again to save the 'last sleep time'.
+ * ----------
+ */
+ }
+ archprintf(fout, "\\.\n\n\n");
+
+ if (ret == -2)
+ {
+ /* copy data transfer failed */
+ write_msg(NULL, "Dumping the contents of table \"%s\" failed: PQgetCopyData() failed.\n", classname);
+ write_msg(NULL, "Error message from server: %s", PQerrorMessage(conn));
+ write_msg(NULL, "The command was: %s\n", q->data);
+ exit_nicely(1);
+ }
+
+ /* Check command status and return to normal libpq state */
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ write_msg(NULL, "Dumping the contents of table \"%s\" failed: PQgetResult() failed.\n", classname);
+ write_msg(NULL, "Error message from server: %s", PQerrorMessage(conn));
+ write_msg(NULL, "The command was: %s\n", q->data);
+ exit_nicely(1);
+ }
+ PQclear(res);
+
+ destroyPQExpBuffer(q);
+ return 1;
+}
+
+/*
+ * Dump table data using INSERT commands.
+ *
+ * Caution: when we restore from an archive file direct to database, the
+ * INSERT commands emitted by this function have to be parsed by
+ * pg_backup_db.c's ExecuteSimpleCommands(), which will not handle comments,
+ * E'' strings, or dollar-quoted strings. So don't emit anything like that.
+ */
+static int
+dumpTableData_insert(Archive *fout, void *dcontext)
+{
+ TableDataInfo *tdinfo = (TableDataInfo *) dcontext;
+ TableInfo *tbinfo = tdinfo->tdtable;
+ const char *classname = tbinfo->dobj.name;
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer insertStmt = NULL;
+ PGresult *res;
+ int tuple;
+ int nfields;
+ int field;
+
+ /*
+ * Make sure we are in proper schema. We will qualify the table name
+ * below anyway (in case its name conflicts with a pg_catalog table); but
+ * this ensures reproducible results in case the table contains regproc,
+ * regclass, etc columns.
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(q, "DECLARE _pg_dump_cursor CURSOR FOR "
+ "SELECT * FROM ONLY %s",
+ fmtQualifiedId(fout->remoteVersion,
+ tbinfo->dobj.namespace->dobj.name,
+ classname));
+ }
+ else
+ {
+ appendPQExpBuffer(q, "DECLARE _pg_dump_cursor CURSOR FOR "
+ "SELECT * FROM %s",
+ fmtQualifiedId(fout->remoteVersion,
+ tbinfo->dobj.namespace->dobj.name,
+ classname));
+ }
+ if (tdinfo->filtercond)
+ appendPQExpBuffer(q, " %s", tdinfo->filtercond);
+
+ ExecuteSqlStatement(fout, q->data);
+
+ while (1)
+ {
+ res = ExecuteSqlQuery(fout, "FETCH 100 FROM _pg_dump_cursor",
+ PGRES_TUPLES_OK);
+ nfields = PQnfields(res);
+ for (tuple = 0; tuple < PQntuples(res); tuple++)
+ {
+ /*
+ * First time through, we build as much of the INSERT statement as
+ * possible in "insertStmt", which we can then just print for each
+ * line. If the table happens to have zero columns then this will
+ * be a complete statement, otherwise it will end in "VALUES(" and
+ * be ready to have the row's column values appended.
+ */
+ if (insertStmt == NULL)
+ {
+ insertStmt = createPQExpBuffer();
+ appendPQExpBuffer(insertStmt, "INSERT INTO %s ",
+ fmtId(classname));
+
+ /* corner case for zero-column table */
+ if (nfields == 0)
+ {
+ appendPQExpBufferStr(insertStmt, "DEFAULT VALUES;\n");
+ }
+ else
+ {
+ /* append the list of column names if required */
+ if (column_inserts)
+ {
+ appendPQExpBufferStr(insertStmt, "(");
+ for (field = 0; field < nfields; field++)
+ {
+ if (field > 0)
+ appendPQExpBufferStr(insertStmt, ", ");
+ appendPQExpBufferStr(insertStmt,
+ fmtId(PQfname(res, field)));
+ }
+ appendPQExpBufferStr(insertStmt, ") ");
+ }
+
+ appendPQExpBufferStr(insertStmt, "VALUES (");
+ }
+ }
+
+ archputs(insertStmt->data, fout);
+
+ /* if it is zero-column table then we're done */
+ if (nfields == 0)
+ continue;
+
+ for (field = 0; field < nfields; field++)
+ {
+ if (field > 0)
+ archputs(", ", fout);
+ if (PQgetisnull(res, tuple, field))
+ {
+ archputs("NULL", fout);
+ continue;
+ }
+
+ /* XXX This code is partially duplicated in ruleutils.c */
+ switch (PQftype(res, field))
+ {
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case OIDOID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case NUMERICOID:
+ {
+ /*
+ * These types are printed without quotes unless
+ * they contain values that aren't accepted by the
+ * scanner unquoted (e.g., 'NaN'). Note that
+ * strtod() and friends might accept NaN, so we
+ * can't use that to test.
+ *
+ * In reality we only need to defend against
+ * infinity and NaN, so we need not get too crazy
+ * about pattern matching here.
+ */
+ const char *s = PQgetvalue(res, tuple, field);
+
+ if (strspn(s, "0123456789 +-eE.") == strlen(s))
+ archputs(s, fout);
+ else
+ archprintf(fout, "'%s'", s);
+ }
+ break;
+
+ case BITOID:
+ case VARBITOID:
+ archprintf(fout, "B'%s'",
+ PQgetvalue(res, tuple, field));
+ break;
+
+ case BOOLOID:
+ if (strcmp(PQgetvalue(res, tuple, field), "t") == 0)
+ archputs("true", fout);
+ else
+ archputs("false", fout);
+ break;
+
+ default:
+ /* All other types are printed as string literals. */
+ resetPQExpBuffer(q);
+ appendStringLiteralAH(q,
+ PQgetvalue(res, tuple, field),
+ fout);
+ archputs(q->data, fout);
+ break;
+ }
+ }
+ archputs(");\n", fout);
+ }
+
+ if (PQntuples(res) <= 0)
+ {
+ PQclear(res);
+ break;
+ }
+ PQclear(res);
+ }
+
+ archputs("\n\n", fout);
+
+ ExecuteSqlStatement(fout, "CLOSE _pg_dump_cursor");
+
+ destroyPQExpBuffer(q);
+ if (insertStmt != NULL)
+ destroyPQExpBuffer(insertStmt);
+
+ return 1;
+}
+
+
+/*
+ * dumpTableData -
+ * dump the contents of a single table
+ *
+ * Actually, this just makes an ArchiveEntry for the table contents.
+ */
+static void
+dumpTableData(Archive *fout, TableDataInfo *tdinfo)
+{
+ TableInfo *tbinfo = tdinfo->tdtable;
+ PQExpBuffer copyBuf = createPQExpBuffer();
+ PQExpBuffer clistBuf = createPQExpBuffer();
+ DataDumperPtr dumpFn;
+ char *copyStmt;
+
+ if (!dump_inserts)
+ {
+ /* Dump/restore using COPY */
+ dumpFn = dumpTableData_copy;
+ /* must use 2 steps here 'cause fmtId is nonreentrant */
+ appendPQExpBuffer(copyBuf, "COPY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(copyBuf, "%s %sFROM stdin;\n",
+ fmtCopyColumnList(tbinfo, clistBuf),
+ (tdinfo->oids && tbinfo->hasoids) ? "WITH OIDS " : "");
+ copyStmt = copyBuf->data;
+ }
+ else
+ {
+ /* Restore using INSERT */
+ dumpFn = dumpTableData_insert;
+ copyStmt = NULL;
+ }
+
+ /*
+ * Note: although the TableDataInfo is a full DumpableObject, we treat its
+ * dependency on its table as "special" and pass it to ArchiveEntry now.
+ * See comments for BuildArchiveDependencies.
+ */
+ ArchiveEntry(fout, tdinfo->dobj.catId, tdinfo->dobj.dumpId,
+ tbinfo->dobj.name, tbinfo->dobj.namespace->dobj.name,
+ NULL, tbinfo->rolname,
+ false, "TABLE DATA", SECTION_DATA,
+ "", "", copyStmt,
+ &(tbinfo->dobj.dumpId), 1,
+ dumpFn, tdinfo);
+
+ destroyPQExpBuffer(copyBuf);
+ destroyPQExpBuffer(clistBuf);
+}
+
+/*
+ * refreshMatViewData -
+ * load or refresh the contents of a single materialized view
+ *
+ * Actually, this just makes an ArchiveEntry for the REFRESH MATERIALIZED VIEW
+ * statement.
+ */
+static void
+refreshMatViewData(Archive *fout, TableDataInfo *tdinfo)
+{
+ TableInfo *tbinfo = tdinfo->tdtable;
+ PQExpBuffer q;
+
+ /* If the materialized view is not flagged as populated, skip this. */
+ if (!tbinfo->relispopulated)
+ return;
+
+ q = createPQExpBuffer();
+
+ appendPQExpBuffer(q, "REFRESH MATERIALIZED VIEW %s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ ArchiveEntry(fout,
+ tdinfo->dobj.catId, /* catalog ID */
+ tdinfo->dobj.dumpId, /* dump ID */
+ tbinfo->dobj.name, /* Name */
+ tbinfo->dobj.namespace->dobj.name, /* Namespace */
+ NULL, /* Tablespace */
+ tbinfo->rolname, /* Owner */
+ false, /* with oids */
+ "MATERIALIZED VIEW DATA", /* Desc */
+ SECTION_POST_DATA, /* Section */
+ q->data, /* Create */
+ "", /* Del */
+ NULL, /* Copy */
+ tdinfo->dobj.dependencies, /* Deps */
+ tdinfo->dobj.nDeps, /* # Deps */
+ NULL, /* Dumper */
+ NULL); /* Dumper Arg */
+
+ destroyPQExpBuffer(q);
+}
+
+/*
+ * getTableData -
+ * set up dumpable objects representing the contents of tables
+ */
+static void
+getTableData(TableInfo *tblinfo, int numTables, bool oids)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ if (tblinfo[i].dobj.dump)
+ makeTableDataInfo(&(tblinfo[i]), oids);
+ }
+}
+
+/*
+ * 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.
+ */
+static void
+makeTableDataInfo(TableInfo *tbinfo, bool oids)
+{
+ TableDataInfo *tdinfo;
+
+ /*
+ * Nothing to do if we already decided to dump the table. This will
+ * happen for "config" tables.
+ */
+ if (tbinfo->dataObj != NULL)
+ return;
+
+ /* Skip VIEWs (no data to dump) */
+ if (tbinfo->relkind == RELKIND_VIEW)
+ return;
+ /* Skip FOREIGN TABLEs (no data to dump) */
+ if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ return;
+
+ /* Don't dump data in unlogged tables, if so requested */
+ if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ no_unlogged_table_data)
+ return;
+
+ /* Check that the data is not explicitly excluded */
+ if (simple_oid_list_member(&tabledata_exclude_oids,
+ tbinfo->dobj.catId.oid))
+ return;
+
+ /* OK, let's dump it */
+ tdinfo = (TableDataInfo *) pg_malloc(sizeof(TableDataInfo));
+
+ if (tbinfo->relkind == RELKIND_MATVIEW)
+ tdinfo->dobj.objType = DO_REFRESH_MATVIEW;
+ else
+ tdinfo->dobj.objType = DO_TABLE_DATA;
+
+ /*
+ * Note: use tableoid 0 so that this object won't be mistaken for
+ * something that pg_depend entries apply to.
+ */
+ tdinfo->dobj.catId.tableoid = 0;
+ tdinfo->dobj.catId.oid = tbinfo->dobj.catId.oid;
+ AssignDumpId(&tdinfo->dobj);
+ tdinfo->dobj.name = tbinfo->dobj.name;
+ tdinfo->dobj.namespace = tbinfo->dobj.namespace;
+ tdinfo->tdtable = tbinfo;
+ tdinfo->oids = oids;
+ tdinfo->filtercond = NULL; /* might get set later */
+ addObjectDependency(&tdinfo->dobj, tbinfo->dobj.dumpId);
+
+ tbinfo->dataObj = tdinfo;
+}
+
+/*
+ * The refresh for a materialized view must be dependent on the refresh for
+ * any materialized view that this one is dependent on.
+ *
+ * This must be called after all the objects are created, but before they are
+ * sorted.
+ */
+static void
+buildMatViewRefreshDependencies(Archive *fout)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ int ntups,
+ i;
+ int i_classid,
+ i_objid,
+ i_refobjid;
+
+ /* No Mat Views before 9.3. */
+ if (fout->remoteVersion < 90300)
+ return;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ query = createPQExpBuffer();
+
+ appendPQExpBufferStr(query, "WITH RECURSIVE w AS "
+ "( "
+ "SELECT d1.objid, d2.refobjid, c2.relkind AS refrelkind "
+ "FROM pg_depend d1 "
+ "JOIN pg_class c1 ON c1.oid = d1.objid "
+ "AND c1.relkind = 'm' "
+ "JOIN pg_rewrite r1 ON r1.ev_class = d1.objid "
+ "JOIN pg_depend d2 ON d2.classid = 'pg_rewrite'::regclass "
+ "AND d2.objid = r1.oid "
+ "AND d2.refobjid <> d1.objid "
+ "JOIN pg_class c2 ON c2.oid = d2.refobjid "
+ "AND c2.relkind IN ('m','v') "
+ "WHERE d1.classid = 'pg_class'::regclass "
+ "UNION "
+ "SELECT w.objid, d3.refobjid, c3.relkind "
+ "FROM w "
+ "JOIN pg_rewrite r3 ON r3.ev_class = w.refobjid "
+ "JOIN pg_depend d3 ON d3.classid = 'pg_rewrite'::regclass "
+ "AND d3.objid = r3.oid "
+ "AND d3.refobjid <> w.refobjid "
+ "JOIN pg_class c3 ON c3.oid = d3.refobjid "
+ "AND c3.relkind IN ('m','v') "
+ ") "
+ "SELECT 'pg_class'::regclass::oid AS classid, objid, refobjid "
+ "FROM w "
+ "WHERE refrelkind = 'm'");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_classid = PQfnumber(res, "classid");
+ i_objid = PQfnumber(res, "objid");
+ i_refobjid = PQfnumber(res, "refobjid");
+
+ for (i = 0; i < ntups; i++)
+ {
+ CatalogId objId;
+ CatalogId refobjId;
+ DumpableObject *dobj;
+ DumpableObject *refdobj;
+ TableInfo *tbinfo;
+ TableInfo *reftbinfo;
+
+ objId.tableoid = atooid(PQgetvalue(res, i, i_classid));
+ objId.oid = atooid(PQgetvalue(res, i, i_objid));
+ refobjId.tableoid = objId.tableoid;
+ refobjId.oid = atooid(PQgetvalue(res, i, i_refobjid));
+
+ dobj = findObjectByCatalogId(objId);
+ if (dobj == NULL)
+ continue;
+
+ Assert(dobj->objType == DO_TABLE);
+ tbinfo = (TableInfo *) dobj;
+ Assert(tbinfo->relkind == RELKIND_MATVIEW);
+ dobj = (DumpableObject *) tbinfo->dataObj;
+ if (dobj == NULL)
+ continue;
+ Assert(dobj->objType == DO_REFRESH_MATVIEW);
+
+ refdobj = findObjectByCatalogId(refobjId);
+ if (refdobj == NULL)
+ continue;
+
+ Assert(refdobj->objType == DO_TABLE);
+ reftbinfo = (TableInfo *) refdobj;
+ Assert(reftbinfo->relkind == RELKIND_MATVIEW);
+ refdobj = (DumpableObject *) reftbinfo->dataObj;
+ if (refdobj == NULL)
+ continue;
+ Assert(refdobj->objType == DO_REFRESH_MATVIEW);
+
+ addObjectDependency(dobj, refdobj->dumpId);
+
+ if (!reftbinfo->relispopulated)
+ tbinfo->relispopulated = false;
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getTableDataFKConstraints -
+ * add dump-order dependencies reflecting foreign key constraints
+ *
+ * This code is executed only in a data-only dump --- in schema+data dumps
+ * we handle foreign key issues by not creating the FK constraints until
+ * after the data is loaded. In a data-only dump, however, we want to
+ * order the table data objects in such a way that a table's referenced
+ * tables are restored first. (In the presence of circular references or
+ * self-references this may be impossible; we'll detect and complain about
+ * that during the dependency sorting step.)
+ */
+static void
+getTableDataFKConstraints(void)
+{
+ DumpableObject **dobjs;
+ int numObjs;
+ int i;
+
+ /* Search through all the dumpable objects for FK constraints */
+ getDumpableObjects(&dobjs, &numObjs);
+ for (i = 0; i < numObjs; i++)
+ {
+ if (dobjs[i]->objType == DO_FK_CONSTRAINT)
+ {
+ ConstraintInfo *cinfo = (ConstraintInfo *) dobjs[i];
+ TableInfo *ftable;
+
+ /* Not interesting unless both tables are to be dumped */
+ if (cinfo->contable == NULL ||
+ cinfo->contable->dataObj == NULL)
+ continue;
+ ftable = findTableByOid(cinfo->confrelid);
+ if (ftable == NULL ||
+ ftable->dataObj == NULL)
+ continue;
+
+ /*
+ * Okay, make referencing table's TABLE_DATA object depend on the
+ * referenced table's TABLE_DATA object.
+ */
+ addObjectDependency(&cinfo->contable->dataObj->dobj,
+ ftable->dataObj->dobj.dumpId);
+ }
+ }
+ free(dobjs);
+}
+
+
+/*
+ * guessConstraintInheritance:
+ * In pre-8.4 databases, we can't tell for certain which constraints
+ * are inherited. We assume a CHECK constraint is inherited if its name
+ * matches the name of any constraint in the parent. Originally this code
+ * tried to compare the expression texts, but that can fail for various
+ * reasons --- for example, if the parent and child tables are in different
+ * schemas, reverse-listing of function calls may produce different text
+ * (schema-qualified or not) depending on search path.
+ *
+ * In 8.4 and up we can rely on the conislocal field to decide which
+ * constraints must be dumped; much safer.
+ *
+ * This function assumes all conislocal flags were initialized to TRUE.
+ * It clears the flag on anything that seems to be inherited.
+ */
+static void
+guessConstraintInheritance(TableInfo *tblinfo, int numTables)
+{
+ int i,
+ j,
+ k;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &(tblinfo[i]);
+ int numParents;
+ TableInfo **parents;
+ TableInfo *parent;
+
+ /* Sequences and views never have parents */
+ if (tbinfo->relkind == RELKIND_SEQUENCE ||
+ tbinfo->relkind == RELKIND_VIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ numParents = tbinfo->numParents;
+ parents = tbinfo->parents;
+
+ if (numParents == 0)
+ continue; /* nothing to see here, move along */
+
+ /* scan for inherited CHECK constraints */
+ for (j = 0; j < tbinfo->ncheck; j++)
+ {
+ ConstraintInfo *constr;
+
+ constr = &(tbinfo->checkexprs[j]);
+
+ for (k = 0; k < numParents; k++)
+ {
+ int l;
+
+ parent = parents[k];
+ for (l = 0; l < parent->ncheck; l++)
+ {
+ ConstraintInfo *pconstr = &(parent->checkexprs[l]);
+
+ if (strcmp(pconstr->dobj.name, constr->dobj.name) == 0)
+ {
+ constr->conislocal = false;
+ break;
+ }
+ }
+ if (!constr->conislocal)
+ break;
+ }
+ }
+ }
+}
+
+
+/*
+ * dumpDatabase:
+ * dump the database definition
+ */
+static void
+dumpDatabase(Archive *fout)
+{
+ PQExpBuffer dbQry = createPQExpBuffer();
+ PQExpBuffer delQry = createPQExpBuffer();
+ PQExpBuffer creaQry = createPQExpBuffer();
+ PGconn *conn = GetConnection(fout);
+ PGresult *res;
+ int i_tableoid,
+ i_oid,
+ i_dba,
+ i_encoding,
+ i_collate,
+ i_ctype,
+ i_frozenxid,
+ i_minmxid,
+ i_tablespace;
+ CatalogId dbCatId;
+ DumpId dbDumpId;
+ const char *datname,
+ *dba,
+ *encoding,
+ *collate,
+ *ctype,
+ *tablespace;
+ uint32 frozenxid, minmxid;
+
+ datname = PQdb(conn);
+
+ if (g_verbose)
+ write_msg(NULL, "saving database definition\n");
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* Get the database owner and parameters from pg_database */
+ if (fout->remoteVersion >= 90300)
+ {
+ appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "datcollate, datctype, datfrozenxid, datminmxid, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
+ "shobj_description(oid, 'pg_database') AS description "
+
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "datcollate, datctype, datfrozenxid, 0 AS datminmxid, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
+ "shobj_description(oid, 'pg_database') AS description "
+
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+ else if (fout->remoteVersion >= 80200)
+ {
+ appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
+ "shobj_description(oid, 'pg_database') AS description "
+
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+ else if (fout->remoteVersion >= 80000)
+ {
+ appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace "
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "NULL AS datcollate, NULL AS datctype, "
+ "0 AS datfrozenxid, 0 AS datminmxid, "
+ "NULL AS tablespace "
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+ else
+ {
+ appendPQExpBuffer(dbQry, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_database') AS tableoid, "
+ "oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "NULL AS datcollate, NULL AS datctype, "
+ "0 AS datfrozenxid, 0 AS datminmxid, "
+ "NULL AS tablespace "
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, dbQry->data);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_dba = PQfnumber(res, "dba");
+ i_encoding = PQfnumber(res, "encoding");
+ i_collate = PQfnumber(res, "datcollate");
+ i_ctype = PQfnumber(res, "datctype");
+ i_frozenxid = PQfnumber(res, "datfrozenxid");
+ i_minmxid = PQfnumber(res, "datminmxid");
+ i_tablespace = PQfnumber(res, "tablespace");
+
+ dbCatId.tableoid = atooid(PQgetvalue(res, 0, i_tableoid));
+ dbCatId.oid = atooid(PQgetvalue(res, 0, i_oid));
+ dba = PQgetvalue(res, 0, i_dba);
+ encoding = PQgetvalue(res, 0, i_encoding);
+ collate = PQgetvalue(res, 0, i_collate);
+ ctype = PQgetvalue(res, 0, i_ctype);
+ frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid));
+ minmxid = atooid(PQgetvalue(res, 0, i_minmxid));
+ tablespace = PQgetvalue(res, 0, i_tablespace);
+
+ appendPQExpBuffer(creaQry, "CREATE DATABASE %s WITH TEMPLATE = template0",
+ fmtId(datname));
+ if (strlen(encoding) > 0)
+ {
+ appendPQExpBufferStr(creaQry, " ENCODING = ");
+ appendStringLiteralAH(creaQry, encoding, fout);
+ }
+ if (strlen(collate) > 0)
+ {
+ appendPQExpBufferStr(creaQry, " LC_COLLATE = ");
+ appendStringLiteralAH(creaQry, collate, fout);
+ }
+ if (strlen(ctype) > 0)
+ {
+ appendPQExpBufferStr(creaQry, " LC_CTYPE = ");
+ appendStringLiteralAH(creaQry, ctype, fout);
+ }
+ if (strlen(tablespace) > 0 && strcmp(tablespace, "pg_default") != 0)
+ appendPQExpBuffer(creaQry, " TABLESPACE = %s",
+ fmtId(tablespace));
+ appendPQExpBufferStr(creaQry, ";\n");
+
+ if (binary_upgrade)
+ {
+ appendPQExpBufferStr(creaQry, "\n-- For binary upgrade, set datfrozenxid and datminmxid.\n");
+ appendPQExpBuffer(creaQry, "UPDATE pg_catalog.pg_database\n"
+ "SET datfrozenxid = '%u', datminmxid = '%u'\n"
+ "WHERE datname = ",
+ frozenxid, minmxid);
+ appendStringLiteralAH(creaQry, datname, fout);
+ appendPQExpBufferStr(creaQry, ";\n");
+
+ }
+
+ appendPQExpBuffer(delQry, "DROP DATABASE %s;\n",
+ fmtId(datname));
+
+ dbDumpId = createDumpId();
+
+ ArchiveEntry(fout,
+ dbCatId, /* catalog ID */
+ dbDumpId, /* dump ID */
+ datname, /* Name */
+ NULL, /* Namespace */
+ NULL, /* Tablespace */
+ dba, /* Owner */
+ false, /* with oids */
+ "DATABASE", /* Desc */
+ SECTION_PRE_DATA, /* Section */
+ creaQry->data, /* Create */
+ delQry->data, /* Del */
+ NULL, /* Copy */
+ NULL, /* Deps */
+ 0, /* # Deps */
+ NULL, /* Dumper */
+ NULL); /* Dumper Arg */
+
+ /*
+ * pg_largeobject and pg_largeobject_metadata come from the old system
+ * intact, so set their relfrozenxids and relminmxids.
+ */
+ if (binary_upgrade)
+ {
+ PGresult *lo_res;
+ PQExpBuffer loFrozenQry = createPQExpBuffer();
+ PQExpBuffer loOutQry = createPQExpBuffer();
+ int i_relfrozenxid, i_relminmxid;
+
+ /*
+ * pg_largeobject
+ */
+ if (fout->remoteVersion >= 90300)
+ appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, relminmxid\n"
+ "FROM pg_catalog.pg_class\n"
+ "WHERE oid = %u;\n",
+ LargeObjectRelationId);
+ else
+ appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, 0 AS relminmxid\n"
+ "FROM pg_catalog.pg_class\n"
+ "WHERE oid = %u;\n",
+ LargeObjectRelationId);
+
+ lo_res = ExecuteSqlQueryForSingleRow(fout, loFrozenQry->data);
+
+ i_relfrozenxid = PQfnumber(lo_res, "relfrozenxid");
+ i_relminmxid = PQfnumber(lo_res, "relminmxid");
+
+ appendPQExpBufferStr(loOutQry, "\n-- For binary upgrade, set pg_largeobject relfrozenxid and relminmxid\n");
+ appendPQExpBuffer(loOutQry, "UPDATE pg_catalog.pg_class\n"
+ "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "WHERE oid = %u;\n",
+ atoi(PQgetvalue(lo_res, 0, i_relfrozenxid)),
+ atoi(PQgetvalue(lo_res, 0, i_relminmxid)),
+ LargeObjectRelationId);
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ "pg_largeobject", NULL, NULL, "",
+ false, "pg_largeobject", SECTION_PRE_DATA,
+ loOutQry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ PQclear(lo_res);
+
+ /*
+ * pg_largeobject_metadata
+ */
+ if (fout->remoteVersion >= 90000)
+ {
+ resetPQExpBuffer(loFrozenQry);
+ resetPQExpBuffer(loOutQry);
+
+ if (fout->remoteVersion >= 90300)
+ appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, relminmxid\n"
+ "FROM pg_catalog.pg_class\n"
+ "WHERE oid = %u;\n",
+ LargeObjectMetadataRelationId);
+ else
+ appendPQExpBuffer(loFrozenQry, "SELECT relfrozenxid, 0 AS relminmxid\n"
+ "FROM pg_catalog.pg_class\n"
+ "WHERE oid = %u;\n",
+ LargeObjectMetadataRelationId);
+
+ lo_res = ExecuteSqlQueryForSingleRow(fout, loFrozenQry->data);
+
+ i_relfrozenxid = PQfnumber(lo_res, "relfrozenxid");
+ i_relminmxid = PQfnumber(lo_res, "relminmxid");
+
+ appendPQExpBufferStr(loOutQry, "\n-- For binary upgrade, set pg_largeobject_metadata relfrozenxid and relminmxid\n");
+ appendPQExpBuffer(loOutQry, "UPDATE pg_catalog.pg_class\n"
+ "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "WHERE oid = %u;\n",
+ atoi(PQgetvalue(lo_res, 0, i_relfrozenxid)),
+ atoi(PQgetvalue(lo_res, 0, i_relminmxid)),
+ LargeObjectMetadataRelationId);
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ "pg_largeobject_metadata", NULL, NULL, "",
+ false, "pg_largeobject_metadata", SECTION_PRE_DATA,
+ loOutQry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ PQclear(lo_res);
+ }
+
+ destroyPQExpBuffer(loFrozenQry);
+ destroyPQExpBuffer(loOutQry);
+ }
+
+ /* Dump DB comment if any */
+ if (fout->remoteVersion >= 80200)
+ {
+ /*
+ * 8.2 keeps comments on shared objects in a shared table, so we
+ * cannot use the dumpComment used for other database objects.
+ */
+ char *comment = PQgetvalue(res, 0, PQfnumber(res, "description"));
+
+ if (comment && strlen(comment))
+ {
+ resetPQExpBuffer(dbQry);
+
+ /*
+ * Generates warning when loaded into a differently-named
+ * database.
+ */
+ appendPQExpBuffer(dbQry, "COMMENT ON DATABASE %s IS ", fmtId(datname));
+ appendStringLiteralAH(dbQry, comment, fout);
+ appendPQExpBufferStr(dbQry, ";\n");
+
+ ArchiveEntry(fout, dbCatId, createDumpId(), datname, NULL, NULL,
+ dba, false, "COMMENT", SECTION_NONE,
+ dbQry->data, "", NULL,
+ &dbDumpId, 1, NULL, NULL);
+ }
+ }
+ else
+ {
+ resetPQExpBuffer(dbQry);
+ appendPQExpBuffer(dbQry, "DATABASE %s", fmtId(datname));
+ dumpComment(fout, dbQry->data, NULL, "",
+ dbCatId, 0, dbDumpId);
+ }
+
+ /* Dump shared security label. */
+ if (!no_security_labels && fout->remoteVersion >= 90200)
+ {
+ PGresult *shres;
+ PQExpBuffer seclabelQry;
+
+ seclabelQry = createPQExpBuffer();
+
+ buildShSecLabelQuery(conn, "pg_database", dbCatId.oid, seclabelQry);
+ shres = ExecuteSqlQuery(fout, seclabelQry->data, PGRES_TUPLES_OK);
+ resetPQExpBuffer(seclabelQry);
+ emitShSecLabels(conn, shres, seclabelQry, "DATABASE", datname);
+ if (strlen(seclabelQry->data))
+ ArchiveEntry(fout, dbCatId, createDumpId(), datname, NULL, NULL,
+ dba, false, "SECURITY LABEL", SECTION_NONE,
+ seclabelQry->data, "", NULL,
+ &dbDumpId, 1, NULL, NULL);
+ destroyPQExpBuffer(seclabelQry);
+ PQclear(shres);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(dbQry);
+ destroyPQExpBuffer(delQry);
+ destroyPQExpBuffer(creaQry);
+}
+
+
+/*
+ * dumpEncoding: put the correct encoding into the archive
+ */
+static void
+dumpEncoding(Archive *AH)
+{
+ const char *encname = pg_encoding_to_char(AH->encoding);
+ PQExpBuffer qry = createPQExpBuffer();
+
+ if (g_verbose)
+ write_msg(NULL, "saving encoding = %s\n", encname);
+
+ appendPQExpBufferStr(qry, "SET client_encoding = ");
+ appendStringLiteralAH(qry, encname, AH);
+ appendPQExpBufferStr(qry, ";\n");
+
+ ArchiveEntry(AH, nilCatalogId, createDumpId(),
+ "ENCODING", NULL, NULL, "",
+ false, "ENCODING", SECTION_PRE_DATA,
+ qry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ destroyPQExpBuffer(qry);
+}
+
+
+/*
+ * dumpStdStrings: put the correct escape string behavior into the archive
+ */
+static void
+dumpStdStrings(Archive *AH)
+{
+ const char *stdstrings = AH->std_strings ? "on" : "off";
+ PQExpBuffer qry = createPQExpBuffer();
+
+ if (g_verbose)
+ write_msg(NULL, "saving standard_conforming_strings = %s\n",
+ stdstrings);
+
+ appendPQExpBuffer(qry, "SET standard_conforming_strings = '%s';\n",
+ stdstrings);
+
+ ArchiveEntry(AH, nilCatalogId, createDumpId(),
+ "STDSTRINGS", NULL, NULL, "",
+ false, "STDSTRINGS", SECTION_PRE_DATA,
+ qry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ destroyPQExpBuffer(qry);
+}
+
+
+/*
+ * getBlobs:
+ * Collect schema-level data about large objects
+ */
+static void
+getBlobs(Archive *fout)
+{
+ PQExpBuffer blobQry = createPQExpBuffer();
+ BlobInfo *binfo;
+ DumpableObject *bdata;
+ PGresult *res;
+ int ntups;
+ int i;
+
+ /* Verbose message */
+ if (g_verbose)
+ write_msg(NULL, "reading large objects\n");
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* Fetch BLOB OIDs, and owner/ACL data if >= 9.0 */
+ if (fout->remoteVersion >= 90000)
+ appendPQExpBuffer(blobQry,
+ "SELECT oid, (%s lomowner) AS rolname, lomacl"
+ " FROM pg_largeobject_metadata",
+ username_subquery);
+ else if (fout->remoteVersion >= 70100)
+ appendPQExpBufferStr(blobQry,
+ "SELECT DISTINCT loid, NULL::oid, NULL::oid"
+ " FROM pg_largeobject");
+ else
+ appendPQExpBufferStr(blobQry,
+ "SELECT oid, NULL::oid, NULL::oid"
+ " FROM pg_class WHERE relkind = 'l'");
+
+ res = ExecuteSqlQuery(fout, blobQry->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ if (ntups > 0)
+ {
+ /*
+ * Each large object has its own BLOB archive entry.
+ */
+ binfo = (BlobInfo *) pg_malloc(ntups * sizeof(BlobInfo));
+
+ for (i = 0; i < ntups; i++)
+ {
+ binfo[i].dobj.objType = DO_BLOB;
+ binfo[i].dobj.catId.tableoid = LargeObjectRelationId;
+ binfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, 0));
+ AssignDumpId(&binfo[i].dobj);
+
+ binfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, 0));
+ if (!PQgetisnull(res, i, 1))
+ binfo[i].rolname = pg_strdup(PQgetvalue(res, i, 1));
+ else
+ binfo[i].rolname = "";
+ if (!PQgetisnull(res, i, 2))
+ binfo[i].blobacl = pg_strdup(PQgetvalue(res, i, 2));
+ else
+ binfo[i].blobacl = NULL;
+ }
+
+ /*
+ * If we have any large objects, a "BLOBS" archive entry is needed.
+ * This is just a placeholder for sorting; it carries no data now.
+ */
+ bdata = (DumpableObject *) pg_malloc(sizeof(DumpableObject));
+ bdata->objType = DO_BLOB_DATA;
+ bdata->catId = nilCatalogId;
+ AssignDumpId(bdata);
+ bdata->name = pg_strdup("BLOBS");
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(blobQry);
+}
+
+/*
+ * dumpBlob
+ *
+ * dump the definition (metadata) of the given large object
+ */
+static void
+dumpBlob(Archive *fout, BlobInfo *binfo)
+{
+ PQExpBuffer cquery = createPQExpBuffer();
+ PQExpBuffer dquery = createPQExpBuffer();
+
+ appendPQExpBuffer(cquery,
+ "SELECT pg_catalog.lo_create('%s');\n",
+ binfo->dobj.name);
+
+ appendPQExpBuffer(dquery,
+ "SELECT pg_catalog.lo_unlink('%s');\n",
+ binfo->dobj.name);
+
+ ArchiveEntry(fout, binfo->dobj.catId, binfo->dobj.dumpId,
+ binfo->dobj.name,
+ NULL, NULL,
+ binfo->rolname, false,
+ "BLOB", SECTION_PRE_DATA,
+ cquery->data, dquery->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* set up tag for comment and/or ACL */
+ resetPQExpBuffer(cquery);
+ appendPQExpBuffer(cquery, "LARGE OBJECT %s", binfo->dobj.name);
+
+ /* Dump comment if any */
+ dumpComment(fout, cquery->data,
+ NULL, binfo->rolname,
+ binfo->dobj.catId, 0, binfo->dobj.dumpId);
+
+ /* Dump security label if any */
+ dumpSecLabel(fout, cquery->data,
+ NULL, binfo->rolname,
+ binfo->dobj.catId, 0, binfo->dobj.dumpId);
+
+ /* Dump ACL if any */
+ if (binfo->blobacl)
+ dumpACL(fout, binfo->dobj.catId, binfo->dobj.dumpId, "LARGE OBJECT",
+ binfo->dobj.name, NULL, cquery->data,
+ NULL, binfo->rolname, binfo->blobacl);
+
+ destroyPQExpBuffer(cquery);
+ destroyPQExpBuffer(dquery);
+}
+
+/*
+ * dumpBlobs:
+ * dump the data contents of all large objects
+ */
+static int
+dumpBlobs(Archive *fout, void *arg)
+{
+ const char *blobQry;
+ const char *blobFetchQry;
+ PGconn *conn = GetConnection(fout);
+ PGresult *res;
+ char buf[LOBBUFSIZE];
+ int ntups;
+ int i;
+ int cnt;
+
+ if (g_verbose)
+ write_msg(NULL, "saving large objects\n");
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * Currently, we re-fetch all BLOB OIDs using a cursor. Consider scanning
+ * the already-in-memory dumpable objects instead...
+ */
+ if (fout->remoteVersion >= 90000)
+ blobQry = "DECLARE bloboid CURSOR FOR SELECT oid FROM pg_largeobject_metadata";
+ else if (fout->remoteVersion >= 70100)
+ blobQry = "DECLARE bloboid CURSOR FOR SELECT DISTINCT loid FROM pg_largeobject";
+ else
+ blobQry = "DECLARE bloboid CURSOR FOR SELECT oid FROM pg_class WHERE relkind = 'l'";
+
+ ExecuteSqlStatement(fout, blobQry);
+
+ /* Command to fetch from cursor */
+ blobFetchQry = "FETCH 1000 IN bloboid";
+
+ do
+ {
+ /* Do a fetch */
+ res = ExecuteSqlQuery(fout, blobFetchQry, PGRES_TUPLES_OK);
+
+ /* Process the tuples, if any */
+ ntups = PQntuples(res);
+ for (i = 0; i < ntups; i++)
+ {
+ Oid blobOid;
+ int loFd;
+
+ blobOid = atooid(PQgetvalue(res, i, 0));
+ /* Open the BLOB */
+ loFd = lo_open(conn, blobOid, INV_READ);
+ if (loFd == -1)
+ exit_horribly(NULL, "could not open large object %u: %s",
+ blobOid, PQerrorMessage(conn));
+
+ StartBlob(fout, blobOid);
+
+ /* Now read it in chunks, sending data to archive */
+ do
+ {
+ cnt = lo_read(conn, loFd, buf, LOBBUFSIZE);
+ if (cnt < 0)
+ exit_horribly(NULL, "error reading large object %u: %s",
+ blobOid, PQerrorMessage(conn));
+
+ WriteData(fout, buf, cnt);
+ } while (cnt > 0);
+
+ lo_close(conn, loFd);
+
+ EndBlob(fout, blobOid);
+ }
+
+ PQclear(res);
+ } while (ntups > 0);
+
+ return 1;
+}
+
+static void
+binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
+ PQExpBuffer upgrade_buffer,
+ Oid pg_type_oid)
+{
+ PQExpBuffer upgrade_query = createPQExpBuffer();
+ PGresult *upgrade_res;
+ Oid pg_type_array_oid;
+
+ appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+ pg_type_oid);
+
+ /* we only support old >= 8.3 for binary upgrades */
+ appendPQExpBuffer(upgrade_query,
+ "SELECT typarray "
+ "FROM pg_catalog.pg_type "
+ "WHERE pg_type.oid = '%u'::pg_catalog.oid;",
+ pg_type_oid);
+
+ upgrade_res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+ pg_type_array_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "typarray")));
+
+ if (OidIsValid(pg_type_array_oid))
+ {
+ appendPQExpBufferStr(upgrade_buffer,
+ "\n-- For binary upgrade, must preserve pg_type array oid\n");
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+ pg_type_array_oid);
+ }
+
+ PQclear(upgrade_res);
+ destroyPQExpBuffer(upgrade_query);
+}
+
+static bool
+binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
+ PQExpBuffer upgrade_buffer,
+ Oid pg_rel_oid)
+{
+ PQExpBuffer upgrade_query = createPQExpBuffer();
+ PGresult *upgrade_res;
+ Oid pg_type_oid;
+ bool toast_set = false;
+
+ /* we only support old >= 8.3 for binary upgrades */
+ appendPQExpBuffer(upgrade_query,
+ "SELECT c.reltype AS crel, t.reltype AS trel "
+ "FROM pg_catalog.pg_class c "
+ "LEFT JOIN pg_catalog.pg_class t ON "
+ " (c.reltoastrelid = t.oid) "
+ "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")));
+
+ binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
+ pg_type_oid);
+
+ if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
+ {
+ /* Toast tables do not have pg_type array rows */
+ Oid pg_type_toast_oid = atooid(PQgetvalue(upgrade_res, 0,
+ PQfnumber(upgrade_res, "trel")));
+
+ appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type toast oid\n");
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_toast_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+ pg_type_toast_oid);
+
+ toast_set = true;
+ }
+
+ PQclear(upgrade_res);
+ destroyPQExpBuffer(upgrade_query);
+
+ return toast_set;
+}
+
+static void
+binary_upgrade_set_pg_class_oids(Archive *fout,
+ PQExpBuffer upgrade_buffer, Oid pg_class_oid,
+ bool is_index)
+{
+ PQExpBuffer upgrade_query = createPQExpBuffer();
+ PGresult *upgrade_res;
+ Oid pg_class_reltoastrelid;
+ Oid pg_index_indexrelid;
+
+ appendPQExpBuffer(upgrade_query,
+ "SELECT c.reltoastrelid, i.indexrelid "
+ "FROM pg_catalog.pg_class c LEFT JOIN "
+ "pg_catalog.pg_index i ON (c.reltoastrelid = i.indrelid AND i.indisvalid) "
+ "WHERE c.oid = '%u'::pg_catalog.oid;",
+ pg_class_oid);
+
+ upgrade_res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+ pg_class_reltoastrelid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "reltoastrelid")));
+ pg_index_indexrelid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "indexrelid")));
+
+ appendPQExpBufferStr(upgrade_buffer,
+ "\n-- For binary upgrade, must preserve pg_class oids\n");
+
+ if (!is_index)
+ {
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_heap_pg_class_oid('%u'::pg_catalog.oid);\n",
+ pg_class_oid);
+ /* only tables have toast tables, not indexes */
+ if (OidIsValid(pg_class_reltoastrelid))
+ {
+ /*
+ * One complexity is that the table definition might not require
+ * the creation of a TOAST table, and the TOAST table might have
+ * been created long after table creation, when the table was
+ * loaded with wide data. By setting the TOAST oid we force
+ * creation of the TOAST heap and TOAST index by the backend so we
+ * can cleanly copy the files during binary upgrade.
+ */
+
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_toast_pg_class_oid('%u'::pg_catalog.oid);\n",
+ pg_class_reltoastrelid);
+
+ /* every toast table has an index */
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_index_pg_class_oid('%u'::pg_catalog.oid);\n",
+ pg_index_indexrelid);
+ }
+ }
+ else
+ appendPQExpBuffer(upgrade_buffer,
+ "SELECT binary_upgrade.set_next_index_pg_class_oid('%u'::pg_catalog.oid);\n",
+ pg_class_oid);
+
+ appendPQExpBufferChar(upgrade_buffer, '\n');
+
+ PQclear(upgrade_res);
+ destroyPQExpBuffer(upgrade_query);
+}
+
+/*
+ * If the DumpableObject is a member of an extension, add a suitable
+ * ALTER EXTENSION ADD command to the creation commands in upgrade_buffer.
+ */
+static void
+binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
+ DumpableObject *dobj,
+ const char *objlabel)
+{
+ DumpableObject *extobj = NULL;
+ int i;
+
+ if (!dobj->ext_member)
+ return;
+
+ /*
+ * Find the parent extension. We could avoid this search if we wanted to
+ * add a link field to DumpableObject, but the space costs of that would
+ * be considerable. We assume that member objects could only have a
+ * direct dependency on their own extension, not any others.
+ */
+ for (i = 0; i < dobj->nDeps; i++)
+ {
+ extobj = findObjectByDumpId(dobj->dependencies[i]);
+ if (extobj && extobj->objType == DO_EXTENSION)
+ break;
+ extobj = NULL;
+ }
+ if (extobj == NULL)
+ exit_horribly(NULL, "could not find parent extension for %s\n", objlabel);
+
+ appendPQExpBufferStr(upgrade_buffer,
+ "\n-- For binary upgrade, handle extension membership the hard way\n");
+ appendPQExpBuffer(upgrade_buffer, "ALTER EXTENSION %s ADD %s;\n",
+ fmtId(extobj->name),
+ objlabel);
+}
+
+/*
+ * getNamespaces:
+ * read all namespaces in the system catalogs and return them in the
+ * NamespaceInfo* structure
+ *
+ * numNamespaces is set to the number of namespaces read in
+ */
+NamespaceInfo *
+getNamespaces(Archive *fout, int *numNamespaces)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ NamespaceInfo *nsinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_nspname;
+ int i_rolname;
+ int i_nspacl;
+
+ /*
+ * Before 7.3, there are no real namespaces; create two dummy entries, one
+ * for user stuff and one for system stuff.
+ */
+ if (fout->remoteVersion < 70300)
+ {
+ nsinfo = (NamespaceInfo *) pg_malloc(2 * sizeof(NamespaceInfo));
+
+ nsinfo[0].dobj.objType = DO_NAMESPACE;
+ nsinfo[0].dobj.catId.tableoid = 0;
+ nsinfo[0].dobj.catId.oid = 0;
+ AssignDumpId(&nsinfo[0].dobj);
+ nsinfo[0].dobj.name = pg_strdup("public");
+ nsinfo[0].rolname = pg_strdup("");
+ nsinfo[0].nspacl = pg_strdup("");
+
+ selectDumpableNamespace(&nsinfo[0]);
+
+ nsinfo[1].dobj.objType = DO_NAMESPACE;
+ nsinfo[1].dobj.catId.tableoid = 0;
+ nsinfo[1].dobj.catId.oid = 1;
+ AssignDumpId(&nsinfo[1].dobj);
+ nsinfo[1].dobj.name = pg_strdup("pg_catalog");
+ nsinfo[1].rolname = pg_strdup("");
+ nsinfo[1].nspacl = pg_strdup("");
+
+ selectDumpableNamespace(&nsinfo[1]);
+
+ *numNamespaces = 2;
+
+ return nsinfo;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * we fetch all namespaces including system ones, so that every object we
+ * read in can be linked to a containing namespace.
+ */
+ appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, "
+ "(%s nspowner) AS rolname, "
+ "nspacl FROM pg_namespace",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ nsinfo = (NamespaceInfo *) pg_malloc(ntups * sizeof(NamespaceInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_nspname = PQfnumber(res, "nspname");
+ i_rolname = PQfnumber(res, "rolname");
+ i_nspacl = PQfnumber(res, "nspacl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ nsinfo[i].dobj.objType = DO_NAMESPACE;
+ nsinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ 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].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ nsinfo[i].nspacl = pg_strdup(PQgetvalue(res, i, i_nspacl));
+
+ /* Decide whether to dump this namespace */
+ selectDumpableNamespace(&nsinfo[i]);
+
+ if (strlen(nsinfo[i].rolname) == 0)
+ write_msg(NULL, "WARNING: owner of schema \"%s\" appears to be invalid\n",
+ nsinfo[i].dobj.name);
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ *numNamespaces = ntups;
+
+ return nsinfo;
+}
+
+/*
+ * findNamespace:
+ * given a namespace OID and an object OID, look up the info read by
+ * getNamespaces
+ *
+ * NB: for pre-7.3 source database, we use object OID to guess whether it's
+ * a system object or not. In 7.3 and later there is no guessing, and we
+ * don't use objoid at all.
+ */
+static NamespaceInfo *
+findNamespace(Archive *fout, Oid nsoid, Oid objoid)
+{
+ NamespaceInfo *nsinfo;
+
+ if (fout->remoteVersion >= 70300)
+ {
+ nsinfo = findNamespaceByOid(nsoid);
+ }
+ else
+ {
+ /* This code depends on the dummy objects set up by getNamespaces. */
+ Oid i;
+
+ if (objoid > g_last_builtin_oid)
+ i = 0; /* user object */
+ else
+ i = 1; /* system object */
+ nsinfo = findNamespaceByOid(i);
+ }
+
+ if (nsinfo == NULL)
+ exit_horribly(NULL, "schema with OID %u does not exist\n", nsoid);
+
+ return nsinfo;
+}
+
+/*
+ * getExtensions:
+ * read all extensions in the system catalogs and return them in the
+ * ExtensionInfo* structure
+ *
+ * numExtensions is set to the number of extensions read in
+ */
+ExtensionInfo *
+getExtensions(Archive *fout, int *numExtensions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ ExtensionInfo *extinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_extname;
+ int i_nspname;
+ int i_extrelocatable;
+ int i_extversion;
+ int i_extconfig;
+ int i_extcondition;
+
+ /*
+ * Before 9.1, there are no extensions.
+ */
+ if (fout->remoteVersion < 90100)
+ {
+ *numExtensions = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBufferStr(query, "SELECT x.tableoid, x.oid, "
+ "x.extname, n.nspname, x.extrelocatable, x.extversion, x.extconfig, x.extcondition "
+ "FROM pg_extension x "
+ "JOIN pg_namespace n ON n.oid = x.extnamespace");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ extinfo = (ExtensionInfo *) pg_malloc(ntups * sizeof(ExtensionInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_extname = PQfnumber(res, "extname");
+ i_nspname = PQfnumber(res, "nspname");
+ i_extrelocatable = PQfnumber(res, "extrelocatable");
+ i_extversion = PQfnumber(res, "extversion");
+ i_extconfig = PQfnumber(res, "extconfig");
+ i_extcondition = PQfnumber(res, "extcondition");
+
+ for (i = 0; i < ntups; i++)
+ {
+ extinfo[i].dobj.objType = DO_EXTENSION;
+ extinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ extinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&extinfo[i].dobj);
+ extinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_extname));
+ extinfo[i].namespace = pg_strdup(PQgetvalue(res, i, i_nspname));
+ extinfo[i].relocatable = *(PQgetvalue(res, i, i_extrelocatable)) == 't';
+ extinfo[i].extversion = pg_strdup(PQgetvalue(res, i, i_extversion));
+ extinfo[i].extconfig = pg_strdup(PQgetvalue(res, i, i_extconfig));
+ extinfo[i].extcondition = pg_strdup(PQgetvalue(res, i, i_extcondition));
+
+ /* Decide whether we want to dump it */
+ selectDumpableExtension(&(extinfo[i]));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ *numExtensions = ntups;
+
+ return extinfo;
+}
+
+/*
+ * getTypes:
+ * read all types in the system catalogs and return them in the
+ * TypeInfo* structure
+ *
+ * numTypes is set to the number of types read in
+ *
+ * NB: this must run after getFuncs() because we assume we can do
+ * findFuncByOid().
+ */
+TypeInfo *
+getTypes(Archive *fout, int *numTypes)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ TypeInfo *tyinfo;
+ ShellTypeInfo *stinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_typname;
+ int i_typnamespace;
+ int i_typacl;
+ int i_rolname;
+ int i_typinput;
+ int i_typoutput;
+ int i_typelem;
+ int i_typrelid;
+ int i_typrelkind;
+ int i_typtype;
+ int i_typisdefined;
+ int i_isarray;
+
+ /*
+ * we include even the built-in types because those may be used as array
+ * elements by user-defined types
+ *
+ * we filter out the built-in types when we dump out the types
+ *
+ * same approach for undefined (shell) types and array types
+ *
+ * Note: as of 8.3 we can reliably detect whether a type is an
+ * auto-generated array type by checking the element type's typarray.
+ * (Before that the test is capable of generating false positives.) We
+ * still check for name beginning with '_', though, so as to avoid the
+ * cost of the subselect probe for all standard types. This would have to
+ * be revisited if the backend ever allows renaming of array types.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 90200)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, typname, "
+ "typnamespace, typacl, "
+ "(%s typowner) AS rolname, "
+ "typinput::oid AS typinput, "
+ "typoutput::oid AS typoutput, typelem, typrelid, "
+ "CASE WHEN typrelid = 0 THEN ' '::\"char\" "
+ "ELSE (SELECT relkind FROM pg_class WHERE oid = typrelid) END AS typrelkind, "
+ "typtype, typisdefined, "
+ "typname[0] = '_' AND typelem != 0 AND "
+ "(SELECT typarray FROM pg_type te WHERE oid = pg_type.typelem) = oid AS isarray "
+ "FROM pg_type",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, typname, "
+ "typnamespace, NULL AS typacl, "
+ "(%s typowner) AS rolname, "
+ "typinput::oid AS typinput, "
+ "typoutput::oid AS typoutput, typelem, typrelid, "
+ "CASE WHEN typrelid = 0 THEN ' '::\"char\" "
+ "ELSE (SELECT relkind FROM pg_class WHERE oid = typrelid) END AS typrelkind, "
+ "typtype, typisdefined, "
+ "typname[0] = '_' AND typelem != 0 AND "
+ "(SELECT typarray FROM pg_type te WHERE oid = pg_type.typelem) = oid AS isarray "
+ "FROM pg_type",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, typname, "
+ "typnamespace, NULL AS typacl, "
+ "(%s typowner) AS rolname, "
+ "typinput::oid AS typinput, "
+ "typoutput::oid AS typoutput, typelem, typrelid, "
+ "CASE WHEN typrelid = 0 THEN ' '::\"char\" "
+ "ELSE (SELECT relkind FROM pg_class WHERE oid = typrelid) END AS typrelkind, "
+ "typtype, typisdefined, "
+ "typname[0] = '_' AND typelem != 0 AS isarray "
+ "FROM pg_type",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, typname, "
+ "0::oid AS typnamespace, NULL AS typacl, "
+ "(%s typowner) AS rolname, "
+ "typinput::oid AS typinput, "
+ "typoutput::oid AS typoutput, typelem, typrelid, "
+ "CASE WHEN typrelid = 0 THEN ' '::\"char\" "
+ "ELSE (SELECT relkind FROM pg_class WHERE oid = typrelid) END AS typrelkind, "
+ "typtype, typisdefined, "
+ "typname[0] = '_' AND typelem != 0 AS isarray "
+ "FROM pg_type",
+ username_subquery);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_type') AS tableoid, "
+ "oid, typname, "
+ "0::oid AS typnamespace, NULL AS typacl, "
+ "(%s typowner) AS rolname, "
+ "typinput::oid AS typinput, "
+ "typoutput::oid AS typoutput, typelem, typrelid, "
+ "CASE WHEN typrelid = 0 THEN ' '::\"char\" "
+ "ELSE (SELECT relkind FROM pg_class WHERE oid = typrelid) END AS typrelkind, "
+ "typtype, typisdefined, "
+ "typname[0] = '_' AND typelem != 0 AS isarray "
+ "FROM pg_type",
+ username_subquery);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ tyinfo = (TypeInfo *) pg_malloc(ntups * sizeof(TypeInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_typname = PQfnumber(res, "typname");
+ i_typnamespace = PQfnumber(res, "typnamespace");
+ i_typacl = PQfnumber(res, "typacl");
+ i_rolname = PQfnumber(res, "rolname");
+ i_typinput = PQfnumber(res, "typinput");
+ i_typoutput = PQfnumber(res, "typoutput");
+ i_typelem = PQfnumber(res, "typelem");
+ i_typrelid = PQfnumber(res, "typrelid");
+ i_typrelkind = PQfnumber(res, "typrelkind");
+ i_typtype = PQfnumber(res, "typtype");
+ i_typisdefined = PQfnumber(res, "typisdefined");
+ i_isarray = PQfnumber(res, "isarray");
+
+ for (i = 0; i < ntups; i++)
+ {
+ tyinfo[i].dobj.objType = DO_TYPE;
+ tyinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ tyinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&tyinfo[i].dobj);
+ tyinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_typname));
+ tyinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_typnamespace)),
+ tyinfo[i].dobj.catId.oid);
+ tyinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ tyinfo[i].typacl = pg_strdup(PQgetvalue(res, i, i_typacl));
+ 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);
+ tyinfo[i].typtype = *PQgetvalue(res, i, i_typtype);
+ tyinfo[i].shellType = NULL;
+
+ if (strcmp(PQgetvalue(res, i, i_typisdefined), "t") == 0)
+ tyinfo[i].isDefined = true;
+ else
+ tyinfo[i].isDefined = false;
+
+ if (strcmp(PQgetvalue(res, i, i_isarray), "t") == 0)
+ tyinfo[i].isArray = true;
+ else
+ tyinfo[i].isArray = false;
+
+ /* Decide whether we want to dump it */
+ selectDumpableType(&tyinfo[i]);
+
+ /*
+ * If it's a domain, fetch info about its constraints, if any
+ */
+ tyinfo[i].nDomChecks = 0;
+ tyinfo[i].domChecks = NULL;
+ if (tyinfo[i].dobj.dump && tyinfo[i].typtype == TYPTYPE_DOMAIN)
+ getDomainConstraints(fout, &(tyinfo[i]));
+
+ /*
+ * If it's a base type, make a DumpableObject representing a shell
+ * definition of the type. We will need to dump that ahead of the I/O
+ * functions for the type. Similarly, range types need a shell
+ * definition in case they have a canonicalize function.
+ *
+ * Note: the shell type doesn't have a catId. You might think it
+ * should copy the base type's catId, but then it might capture the
+ * pg_depend entries for the type, which we don't want.
+ */
+ if (tyinfo[i].dobj.dump && (tyinfo[i].typtype == TYPTYPE_BASE ||
+ tyinfo[i].typtype == TYPTYPE_RANGE))
+ {
+ stinfo = (ShellTypeInfo *) pg_malloc(sizeof(ShellTypeInfo));
+ stinfo->dobj.objType = DO_SHELL_TYPE;
+ stinfo->dobj.catId = nilCatalogId;
+ AssignDumpId(&stinfo->dobj);
+ stinfo->dobj.name = pg_strdup(tyinfo[i].dobj.name);
+ stinfo->dobj.namespace = tyinfo[i].dobj.namespace;
+ stinfo->baseType = &(tyinfo[i]);
+ tyinfo[i].shellType = stinfo;
+
+ /*
+ * Initially mark the shell type as not to be dumped. We'll only
+ * dump it if the I/O or canonicalize functions need to be dumped;
+ * this is taken care of while sorting dependencies.
+ */
+ stinfo->dobj.dump = false;
+
+ /*
+ * However, if dumping from pre-7.3, there will be no dependency
+ * info so we have to fake it here. We only need to worry about
+ * typinput and typoutput since the other functions only exist
+ * post-7.3.
+ */
+ if (fout->remoteVersion < 70300)
+ {
+ Oid typinput;
+ Oid typoutput;
+ FuncInfo *funcInfo;
+
+ typinput = atooid(PQgetvalue(res, i, i_typinput));
+ typoutput = atooid(PQgetvalue(res, i, i_typoutput));
+
+ funcInfo = findFuncByOid(typinput);
+ if (funcInfo && funcInfo->dobj.dump)
+ {
+ /* base type depends on function */
+ addObjectDependency(&tyinfo[i].dobj,
+ funcInfo->dobj.dumpId);
+ /* function depends on shell type */
+ addObjectDependency(&funcInfo->dobj,
+ stinfo->dobj.dumpId);
+ /* mark shell type as to be dumped */
+ stinfo->dobj.dump = true;
+ }
+
+ funcInfo = findFuncByOid(typoutput);
+ if (funcInfo && funcInfo->dobj.dump)
+ {
+ /* base type depends on function */
+ addObjectDependency(&tyinfo[i].dobj,
+ funcInfo->dobj.dumpId);
+ /* function depends on shell type */
+ addObjectDependency(&funcInfo->dobj,
+ stinfo->dobj.dumpId);
+ /* mark shell type as to be dumped */
+ stinfo->dobj.dump = true;
+ }
+ }
+ }
+
+ if (strlen(tyinfo[i].rolname) == 0)
+ write_msg(NULL, "WARNING: owner of data type \"%s\" appears to be invalid\n",
+ tyinfo[i].dobj.name);
+ }
+
+ *numTypes = ntups;
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return tyinfo;
+}
+
+/*
+ * getOperators:
+ * read all operators in the system catalogs and return them in the
+ * OprInfo* structure
+ *
+ * numOprs is set to the number of operators read in
+ */
+OprInfo *
+getOperators(Archive *fout, int *numOprs)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ OprInfo *oprinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_oprname;
+ int i_oprnamespace;
+ int i_rolname;
+ int i_oprkind;
+ int i_oprcode;
+
+ /*
+ * find all operators, including builtin operators; we filter out
+ * system-defined operators at dump-out time.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, oprname, "
+ "oprnamespace, "
+ "(%s oprowner) AS rolname, "
+ "oprkind, "
+ "oprcode::oid AS oprcode "
+ "FROM pg_operator",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, oprname, "
+ "0::oid AS oprnamespace, "
+ "(%s oprowner) AS rolname, "
+ "oprkind, "
+ "oprcode::oid AS oprcode "
+ "FROM pg_operator",
+ username_subquery);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_operator') AS tableoid, "
+ "oid, oprname, "
+ "0::oid AS oprnamespace, "
+ "(%s oprowner) AS rolname, "
+ "oprkind, "
+ "oprcode::oid AS oprcode "
+ "FROM pg_operator",
+ username_subquery);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numOprs = ntups;
+
+ oprinfo = (OprInfo *) pg_malloc(ntups * sizeof(OprInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_oprname = PQfnumber(res, "oprname");
+ i_oprnamespace = PQfnumber(res, "oprnamespace");
+ i_rolname = PQfnumber(res, "rolname");
+ i_oprkind = PQfnumber(res, "oprkind");
+ i_oprcode = PQfnumber(res, "oprcode");
+
+ for (i = 0; i < ntups; i++)
+ {
+ oprinfo[i].dobj.objType = DO_OPERATOR;
+ oprinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ oprinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&oprinfo[i].dobj);
+ oprinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_oprname));
+ oprinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_oprnamespace)),
+ oprinfo[i].dobj.catId.oid);
+ oprinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ oprinfo[i].oprkind = (PQgetvalue(res, i, i_oprkind))[0];
+ oprinfo[i].oprcode = atooid(PQgetvalue(res, i, i_oprcode));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(oprinfo[i].dobj));
+
+ if (strlen(oprinfo[i].rolname) == 0)
+ write_msg(NULL, "WARNING: owner of operator \"%s\" appears to be invalid\n",
+ oprinfo[i].dobj.name);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return oprinfo;
+}
+
+/*
+ * getCollations:
+ * read all collations in the system catalogs and return them in the
+ * CollInfo* structure
+ *
+ * numCollations is set to the number of collations read in
+ */
+CollInfo *
+getCollations(Archive *fout, int *numCollations)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ CollInfo *collinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_collname;
+ int i_collnamespace;
+ int i_rolname;
+
+ /* Collations didn't exist pre-9.1 */
+ if (fout->remoteVersion < 90100)
+ {
+ *numCollations = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /*
+ * find all collations, including builtin collations; we filter out
+ * system-defined collations at dump-out time.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, collname, "
+ "collnamespace, "
+ "(%s collowner) AS rolname "
+ "FROM pg_collation",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numCollations = ntups;
+
+ collinfo = (CollInfo *) pg_malloc(ntups * sizeof(CollInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_collname = PQfnumber(res, "collname");
+ i_collnamespace = PQfnumber(res, "collnamespace");
+ i_rolname = PQfnumber(res, "rolname");
+
+ for (i = 0; i < ntups; i++)
+ {
+ collinfo[i].dobj.objType = DO_COLLATION;
+ collinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ collinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&collinfo[i].dobj);
+ collinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_collname));
+ collinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_collnamespace)),
+ collinfo[i].dobj.catId.oid);
+ collinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(collinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return collinfo;
+}
+
+/*
+ * getConversions:
+ * read all conversions in the system catalogs and return them in the
+ * ConvInfo* structure
+ *
+ * numConversions is set to the number of conversions read in
+ */
+ConvInfo *
+getConversions(Archive *fout, int *numConversions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ ConvInfo *convinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_conname;
+ int i_connamespace;
+ int i_rolname;
+
+ /* Conversions didn't exist pre-7.3 */
+ if (fout->remoteVersion < 70300)
+ {
+ *numConversions = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /*
+ * find all conversions, including builtin conversions; we filter out
+ * system-defined conversions at dump-out time.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, conname, "
+ "connamespace, "
+ "(%s conowner) AS rolname "
+ "FROM pg_conversion",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numConversions = ntups;
+
+ convinfo = (ConvInfo *) pg_malloc(ntups * sizeof(ConvInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_conname = PQfnumber(res, "conname");
+ i_connamespace = PQfnumber(res, "connamespace");
+ i_rolname = PQfnumber(res, "rolname");
+
+ for (i = 0; i < ntups; i++)
+ {
+ convinfo[i].dobj.objType = DO_CONVERSION;
+ convinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ convinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&convinfo[i].dobj);
+ convinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_conname));
+ convinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_connamespace)),
+ convinfo[i].dobj.catId.oid);
+ convinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(convinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return convinfo;
+}
+
+/*
+ * getOpclasses:
+ * read all opclasses in the system catalogs and return them in the
+ * OpclassInfo* structure
+ *
+ * numOpclasses is set to the number of opclasses read in
+ */
+OpclassInfo *
+getOpclasses(Archive *fout, int *numOpclasses)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ OpclassInfo *opcinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_opcname;
+ int i_opcnamespace;
+ int i_rolname;
+
+ /*
+ * find all opclasses, including builtin opclasses; we filter out
+ * system-defined opclasses at dump-out time.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, opcname, "
+ "opcnamespace, "
+ "(%s opcowner) AS rolname "
+ "FROM pg_opclass",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, opcname, "
+ "0::oid AS opcnamespace, "
+ "''::name AS rolname "
+ "FROM pg_opclass");
+ }
+ else
+ {
+ appendPQExpBufferStr(query, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_opclass') AS tableoid, "
+ "oid, opcname, "
+ "0::oid AS opcnamespace, "
+ "''::name AS rolname "
+ "FROM pg_opclass");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numOpclasses = ntups;
+
+ opcinfo = (OpclassInfo *) pg_malloc(ntups * sizeof(OpclassInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_opcname = PQfnumber(res, "opcname");
+ i_opcnamespace = PQfnumber(res, "opcnamespace");
+ i_rolname = PQfnumber(res, "rolname");
+
+ for (i = 0; i < ntups; i++)
+ {
+ opcinfo[i].dobj.objType = DO_OPCLASS;
+ opcinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ opcinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&opcinfo[i].dobj);
+ opcinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_opcname));
+ opcinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_opcnamespace)),
+ opcinfo[i].dobj.catId.oid);
+ opcinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(opcinfo[i].dobj));
+
+ if (fout->remoteVersion >= 70300)
+ {
+ if (strlen(opcinfo[i].rolname) == 0)
+ write_msg(NULL, "WARNING: owner of operator class \"%s\" appears to be invalid\n",
+ opcinfo[i].dobj.name);
+ }
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return opcinfo;
+}
+
+/*
+ * getOpfamilies:
+ * read all opfamilies in the system catalogs and return them in the
+ * OpfamilyInfo* structure
+ *
+ * numOpfamilies is set to the number of opfamilies read in
+ */
+OpfamilyInfo *
+getOpfamilies(Archive *fout, int *numOpfamilies)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ OpfamilyInfo *opfinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_opfname;
+ int i_opfnamespace;
+ int i_rolname;
+
+ /* Before 8.3, there is no separate concept of opfamilies */
+ if (fout->remoteVersion < 80300)
+ {
+ *numOpfamilies = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /*
+ * find all opfamilies, including builtin opfamilies; we filter out
+ * system-defined opfamilies at dump-out time.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, opfname, "
+ "opfnamespace, "
+ "(%s opfowner) AS rolname "
+ "FROM pg_opfamily",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numOpfamilies = ntups;
+
+ opfinfo = (OpfamilyInfo *) pg_malloc(ntups * sizeof(OpfamilyInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_opfname = PQfnumber(res, "opfname");
+ i_opfnamespace = PQfnumber(res, "opfnamespace");
+ i_rolname = PQfnumber(res, "rolname");
+
+ for (i = 0; i < ntups; i++)
+ {
+ opfinfo[i].dobj.objType = DO_OPFAMILY;
+ opfinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ opfinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&opfinfo[i].dobj);
+ opfinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_opfname));
+ opfinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_opfnamespace)),
+ opfinfo[i].dobj.catId.oid);
+ opfinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(opfinfo[i].dobj));
+
+ if (fout->remoteVersion >= 70300)
+ {
+ if (strlen(opfinfo[i].rolname) == 0)
+ write_msg(NULL, "WARNING: owner of operator family \"%s\" appears to be invalid\n",
+ opfinfo[i].dobj.name);
+ }
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return opfinfo;
+}
+
+/*
+ * getAggregates:
+ * read all the user-defined aggregates in the system catalogs and
+ * return them in the AggInfo* structure
+ *
+ * numAggs is set to the number of aggregates read in
+ */
+AggInfo *
+getAggregates(Archive *fout, int *numAggs)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ AggInfo *agginfo;
+ int i_tableoid;
+ int i_oid;
+ int i_aggname;
+ int i_aggnamespace;
+ int i_pronargs;
+ int i_proargtypes;
+ int i_rolname;
+ int i_aggacl;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * Find all user-defined aggregates. See comment in getFuncs() for the
+ * rationale behind the filtering logic.
+ */
+
+ if (fout->remoteVersion >= 80200)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, "
+ "pronamespace AS aggnamespace, "
+ "pronargs, proargtypes, "
+ "(%s proowner) AS rolname, "
+ "proacl AS aggacl "
+ "FROM pg_proc p "
+ "WHERE proisagg AND ("
+ "pronamespace != "
+ "(SELECT oid FROM pg_namespace "
+ "WHERE nspname = 'pg_catalog')",
+ username_subquery);
+ if (binary_upgrade && fout->remoteVersion >= 90100)
+ 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 >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, "
+ "pronamespace AS aggnamespace, "
+ "CASE WHEN proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype THEN 0 ELSE 1 END AS pronargs, "
+ "proargtypes, "
+ "(%s proowner) AS rolname, "
+ "proacl AS aggacl "
+ "FROM pg_proc "
+ "WHERE proisagg "
+ "AND pronamespace != "
+ "(SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog')",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, aggname, "
+ "0::oid AS aggnamespace, "
+ "CASE WHEN aggbasetype = 0 THEN 0 ELSE 1 END AS pronargs, "
+ "aggbasetype AS proargtypes, "
+ "(%s aggowner) AS rolname, "
+ "NULL AS aggacl "
+ "FROM pg_aggregate "
+ "where oid > '%u'::oid",
+ username_subquery,
+ g_last_builtin_oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_aggregate') AS tableoid, "
+ "oid, aggname, "
+ "0::oid AS aggnamespace, "
+ "CASE WHEN aggbasetype = 0 THEN 0 ELSE 1 END AS pronargs, "
+ "aggbasetype AS proargtypes, "
+ "(%s aggowner) AS rolname, "
+ "NULL AS aggacl "
+ "FROM pg_aggregate "
+ "where oid > '%u'::oid",
+ username_subquery,
+ g_last_builtin_oid);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numAggs = ntups;
+
+ agginfo = (AggInfo *) pg_malloc(ntups * sizeof(AggInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_aggname = PQfnumber(res, "aggname");
+ i_aggnamespace = PQfnumber(res, "aggnamespace");
+ i_pronargs = PQfnumber(res, "pronargs");
+ i_proargtypes = PQfnumber(res, "proargtypes");
+ i_rolname = PQfnumber(res, "rolname");
+ i_aggacl = PQfnumber(res, "aggacl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ agginfo[i].aggfn.dobj.objType = DO_AGG;
+ agginfo[i].aggfn.dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ agginfo[i].aggfn.dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&agginfo[i].aggfn.dobj);
+ agginfo[i].aggfn.dobj.name = pg_strdup(PQgetvalue(res, i, i_aggname));
+ agginfo[i].aggfn.dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_aggnamespace)),
+ agginfo[i].aggfn.dobj.catId.oid);
+ agginfo[i].aggfn.rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ if (strlen(agginfo[i].aggfn.rolname) == 0)
+ write_msg(NULL, "WARNING: owner of aggregate function \"%s\" appears to be invalid\n",
+ 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.nargs = atoi(PQgetvalue(res, i, i_pronargs));
+ if (agginfo[i].aggfn.nargs == 0)
+ agginfo[i].aggfn.argtypes = NULL;
+ else
+ {
+ agginfo[i].aggfn.argtypes = (Oid *) pg_malloc(agginfo[i].aggfn.nargs * sizeof(Oid));
+ if (fout->remoteVersion >= 70300)
+ parseOidArray(PQgetvalue(res, i, i_proargtypes),
+ agginfo[i].aggfn.argtypes,
+ agginfo[i].aggfn.nargs);
+ else
+ /* it's just aggbasetype */
+ agginfo[i].aggfn.argtypes[0] = atooid(PQgetvalue(res, i, i_proargtypes));
+ }
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(agginfo[i].aggfn.dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return agginfo;
+}
+
+/*
+ * getFuncs:
+ * read all the user-defined functions in the system catalogs and
+ * return them in the FuncInfo* structure
+ *
+ * numFuncs is set to the number of functions read in
+ */
+FuncInfo *
+getFuncs(Archive *fout, int *numFuncs)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ FuncInfo *finfo;
+ int i_tableoid;
+ int i_oid;
+ int i_proname;
+ int i_pronamespace;
+ int i_rolname;
+ int i_prolang;
+ int i_pronargs;
+ int i_proargtypes;
+ int i_prorettype;
+ int i_proacl;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * Find all user-defined functions. Normally we can exclude functions in
+ * pg_catalog, which is worth doing since there are several thousand of
+ * 'em. However, there are some extensions that create functions in
+ * pg_catalog. In normal dumps we can still ignore those --- but in
+ * binary-upgrade mode, we must dump the member objects of the extension,
+ * so be sure to fetch any such functions.
+ *
+ * Also, in 9.2 and up, exclude functions that are internally dependent on
+ * something else, since presumably those will be created as a result of
+ * creating the something else. This currently only acts to suppress
+ * constructor functions for range types. Note that this is OK only
+ * because the constructors don't have any dependencies the range type
+ * doesn't have; otherwise we might not get creation ordering correct.
+ */
+
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query,
+ "SELECT tableoid, oid, proname, prolang, "
+ "pronargs, proargtypes, prorettype, proacl, "
+ "pronamespace, "
+ "(%s proowner) AS rolname "
+ "FROM pg_proc p "
+ "WHERE NOT proisagg AND ("
+ "pronamespace != "
+ "(SELECT oid FROM pg_namespace "
+ "WHERE nspname = 'pg_catalog')",
+ username_subquery);
+ if (fout->remoteVersion >= 90200)
+ appendPQExpBufferStr(query,
+ "\n AND NOT EXISTS (SELECT 1 FROM pg_depend "
+ "WHERE classid = 'pg_proc'::regclass AND "
+ "objid = p.oid AND deptype = 'i')");
+ if (binary_upgrade && fout->remoteVersion >= 90100)
+ appendPQExpBufferStr(query,
+ "\n 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 >= 70100)
+ {
+ appendPQExpBuffer(query,
+ "SELECT tableoid, oid, proname, prolang, "
+ "pronargs, proargtypes, prorettype, "
+ "NULL AS proacl, "
+ "0::oid AS pronamespace, "
+ "(%s proowner) AS rolname "
+ "FROM pg_proc "
+ "WHERE pg_proc.oid > '%u'::oid",
+ username_subquery,
+ g_last_builtin_oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query,
+ "SELECT "
+ "(SELECT oid FROM pg_class "
+ " WHERE relname = 'pg_proc') AS tableoid, "
+ "oid, proname, prolang, "
+ "pronargs, proargtypes, prorettype, "
+ "NULL AS proacl, "
+ "0::oid AS pronamespace, "
+ "(%s proowner) AS rolname "
+ "FROM pg_proc "
+ "where pg_proc.oid > '%u'::oid",
+ username_subquery,
+ g_last_builtin_oid);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numFuncs = ntups;
+
+ finfo = (FuncInfo *) pg_malloc0(ntups * sizeof(FuncInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_proname = PQfnumber(res, "proname");
+ i_pronamespace = PQfnumber(res, "pronamespace");
+ i_rolname = PQfnumber(res, "rolname");
+ i_prolang = PQfnumber(res, "prolang");
+ i_pronargs = PQfnumber(res, "pronargs");
+ i_proargtypes = PQfnumber(res, "proargtypes");
+ i_prorettype = PQfnumber(res, "prorettype");
+ i_proacl = PQfnumber(res, "proacl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ finfo[i].dobj.objType = DO_FUNC;
+ finfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ finfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&finfo[i].dobj);
+ finfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_proname));
+ finfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_pronamespace)),
+ finfo[i].dobj.catId.oid);
+ 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].nargs = atoi(PQgetvalue(res, i, i_pronargs));
+ if (finfo[i].nargs == 0)
+ finfo[i].argtypes = NULL;
+ else
+ {
+ finfo[i].argtypes = (Oid *) pg_malloc(finfo[i].nargs * sizeof(Oid));
+ parseOidArray(PQgetvalue(res, i, i_proargtypes),
+ finfo[i].argtypes, finfo[i].nargs);
+ }
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(finfo[i].dobj));
+
+ if (strlen(finfo[i].rolname) == 0)
+ write_msg(NULL,
+ "WARNING: owner of function \"%s\" appears to be invalid\n",
+ finfo[i].dobj.name);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return finfo;
+}
+
+/*
+ * getTables
+ * read all the user-defined tables (no indexes, no catalogs)
+ * in the system catalogs return them in the TableInfo* structure
+ *
+ * numTables is set to the number of tables read in
+ */
+TableInfo *
+getTables(Archive *fout, int *numTables)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ TableInfo *tblinfo;
+ int i_reltableoid;
+ int i_reloid;
+ int i_relname;
+ int i_relnamespace;
+ int i_relkind;
+ int i_relacl;
+ int i_rolname;
+ int i_relchecks;
+ int i_relhastriggers;
+ int i_relhasindex;
+ int i_relhasrules;
+ int i_relhasoids;
+ int i_relfrozenxid;
+ int i_relminmxid;
+ int i_toastoid;
+ int i_toastfrozenxid;
+ int i_toastminmxid;
+ int i_relpersistence;
+ int i_relispopulated;
+ int i_relreplident;
+ int i_owning_tab;
+ int i_owning_col;
+ int i_reltablespace;
+ int i_reloptions;
+ int i_checkoption;
+ int i_toastreloptions;
+ int i_reloftype;
+ int i_relpages;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * Find all the tables and table-like objects.
+ *
+ * We include system catalogs, so that we can work if a user table is
+ * defined to inherit from a system catalog (pretty weird, but...)
+ *
+ * We ignore relations that are not ordinary tables, sequences, views,
+ * materialized views, composite types, or foreign tables.
+ *
+ * Composite-type table entries won't be dumped as such, but we have to
+ * make a DumpableObject for them so that we can track dependencies of the
+ * composite type (pg_depend entries for columns of the composite type
+ * link to the pg_class entry not the pg_type entry).
+ *
+ * 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.
+ */
+
+ if (fout->remoteVersion >= 90400)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "c.relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ }
+ else if (fout->remoteVersion >= 90300)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "'d' AS relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ }
+ else if (fout->remoteVersion >= 90100)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "0 AS tminmxid, "
+ "c.relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(c.reloptions, ', ') AS reloptions, "
+ "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ }
+ else if (fout->remoteVersion >= 90000)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "0 AS tminmxid, "
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(c.reloptions, ', ') AS reloptions, "
+ "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "0 AS tminmxid, "
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, c.relpages, "
+ "NULL AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(c.reloptions, ', ') AS reloptions, "
+ "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
+ }
+ else if (fout->remoteVersion >= 80200)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "0 AS tminmxid, "
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, c.relpages, "
+ "NULL AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(c.reloptions, ', ') AS reloptions, "
+ "NULL AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
+ }
+ else if (fout->remoteVersion >= 80000)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, relname, "
+ "relacl, relkind, relnamespace, "
+ "(%s relowner) AS rolname, "
+ "relchecks, (reltriggers <> 0) AS relhastriggers, "
+ "relhasindex, relhasrules, relhasoids, "
+ "0 AS relfrozenxid, 0 AS relminmxid,"
+ "0 AS toid, "
+ "0 AS tfrozenxid, 0 AS tminmxid,"
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, relpages, "
+ "NULL AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "NULL AS reloptions, "
+ "NULL AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'i') "
+ "WHERE relkind in ('%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, relname, "
+ "relacl, relkind, relnamespace, "
+ "(%s relowner) AS rolname, "
+ "relchecks, (reltriggers <> 0) AS relhastriggers, "
+ "relhasindex, relhasrules, relhasoids, "
+ "0 AS relfrozenxid, 0 AS relminmxid,"
+ "0 AS toid, "
+ "0 AS tfrozenxid, 0 AS tminmxid,"
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, relpages, "
+ "NULL AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "NULL AS reltablespace, "
+ "NULL AS reloptions, "
+ "NULL AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'i') "
+ "WHERE relkind IN ('%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
+ }
+ else if (fout->remoteVersion >= 70200)
+ {
+ appendPQExpBuffer(query,
+ "SELECT tableoid, oid, relname, relacl, relkind, "
+ "0::oid AS relnamespace, "
+ "(%s relowner) AS rolname, "
+ "relchecks, (reltriggers <> 0) AS relhastriggers, "
+ "relhasindex, relhasrules, relhasoids, "
+ "0 AS relfrozenxid, 0 AS relminmxid,"
+ "0 AS toid, "
+ "0 AS tfrozenxid, 0 AS tminmxid,"
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, relpages, "
+ "NULL AS reloftype, "
+ "NULL::oid AS owning_tab, "
+ "NULL::int4 AS owning_col, "
+ "NULL AS reltablespace, "
+ "NULL AS reloptions, "
+ "NULL AS toast_reloptions "
+ "FROM pg_class "
+ "WHERE relkind IN ('%c', '%c', '%c') "
+ "ORDER BY oid",
+ username_subquery,
+ RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ /* all tables have oids in 7.1 */
+ appendPQExpBuffer(query,
+ "SELECT tableoid, oid, relname, relacl, relkind, "
+ "0::oid AS relnamespace, "
+ "(%s relowner) AS rolname, "
+ "relchecks, (reltriggers <> 0) AS relhastriggers, "
+ "relhasindex, relhasrules, "
+ "'t'::bool AS relhasoids, "
+ "0 AS relfrozenxid, 0 AS relminmxid,"
+ "0 AS toid, "
+ "0 AS tfrozenxid, 0 AS tminmxid,"
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, relpages, "
+ "NULL AS reloftype, "
+ "NULL::oid AS owning_tab, "
+ "NULL::int4 AS owning_col, "
+ "NULL AS reltablespace, "
+ "NULL AS reloptions, "
+ "NULL AS toast_reloptions "
+ "FROM pg_class "
+ "WHERE relkind IN ('%c', '%c', '%c') "
+ "ORDER BY oid",
+ username_subquery,
+ RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW);
+ }
+ else
+ {
+ /*
+ * Before 7.1, view relkind was not set to 'v', so we must check if we
+ * have a view by looking for a rule in pg_rewrite.
+ */
+ appendPQExpBuffer(query,
+ "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_class') AS tableoid, "
+ "oid, relname, relacl, "
+ "CASE WHEN relhasrules and relkind = 'r' "
+ " and EXISTS(SELECT rulename FROM pg_rewrite r WHERE "
+ " r.ev_class = c.oid AND r.ev_type = '1') "
+ "THEN '%c'::\"char\" "
+ "ELSE relkind END AS relkind,"
+ "0::oid AS relnamespace, "
+ "(%s relowner) AS rolname, "
+ "relchecks, (reltriggers <> 0) AS relhastriggers, "
+ "relhasindex, relhasrules, "
+ "'t'::bool AS relhasoids, "
+ "0 AS relfrozenxid, 0 AS relminmxid,"
+ "0 AS toid, "
+ "0 AS tfrozenxid, 0 AS tminmxid,"
+ "'p' AS relpersistence, 't' as relispopulated, "
+ "'d' AS relreplident, 0 AS relpages, "
+ "NULL AS reloftype, "
+ "NULL::oid AS owning_tab, "
+ "NULL::int4 AS owning_col, "
+ "NULL AS reltablespace, "
+ "NULL AS reloptions, "
+ "NULL AS toast_reloptions "
+ "FROM pg_class c "
+ "WHERE relkind IN ('%c', '%c') "
+ "ORDER BY oid",
+ RELKIND_VIEW,
+ username_subquery,
+ RELKIND_RELATION, RELKIND_SEQUENCE);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numTables = ntups;
+
+ /*
+ * Extract data from result and lock dumpable tables. We do the locking
+ * before anything else, to minimize the window wherein a table could
+ * disappear under us.
+ *
+ * Note that we have to save info about all tables here, even when dumping
+ * only one, because we don't yet know which tables might be inheritance
+ * ancestors of the target table.
+ */
+ tblinfo = (TableInfo *) pg_malloc0(ntups * sizeof(TableInfo));
+
+ i_reltableoid = PQfnumber(res, "tableoid");
+ i_reloid = PQfnumber(res, "oid");
+ i_relname = PQfnumber(res, "relname");
+ i_relnamespace = PQfnumber(res, "relnamespace");
+ i_relacl = PQfnumber(res, "relacl");
+ i_relkind = PQfnumber(res, "relkind");
+ i_rolname = PQfnumber(res, "rolname");
+ i_relchecks = PQfnumber(res, "relchecks");
+ i_relhastriggers = PQfnumber(res, "relhastriggers");
+ i_relhasindex = PQfnumber(res, "relhasindex");
+ i_relhasrules = PQfnumber(res, "relhasrules");
+ i_relhasoids = PQfnumber(res, "relhasoids");
+ i_relfrozenxid = PQfnumber(res, "relfrozenxid");
+ i_relminmxid = PQfnumber(res, "relminmxid");
+ i_toastoid = PQfnumber(res, "toid");
+ i_toastfrozenxid = PQfnumber(res, "tfrozenxid");
+ i_toastminmxid = PQfnumber(res, "tminmxid");
+ i_relpersistence = PQfnumber(res, "relpersistence");
+ i_relispopulated = PQfnumber(res, "relispopulated");
+ i_relreplident = PQfnumber(res, "relreplident");
+ i_relpages = PQfnumber(res, "relpages");
+ i_owning_tab = PQfnumber(res, "owning_tab");
+ i_owning_col = PQfnumber(res, "owning_col");
+ i_reltablespace = PQfnumber(res, "reltablespace");
+ i_reloptions = PQfnumber(res, "reloptions");
+ i_checkoption = PQfnumber(res, "checkoption");
+ i_toastreloptions = PQfnumber(res, "toast_reloptions");
+ i_reloftype = PQfnumber(res, "reloftype");
+
+ if (lockWaitTimeout && fout->remoteVersion >= 70300)
+ {
+ /*
+ * Arrange to fail instead of waiting forever for a table lock.
+ *
+ * NB: this coding assumes that the only queries issued within the
+ * following loop are LOCK TABLEs; else the timeout may be undesirably
+ * applied to other things too.
+ */
+ resetPQExpBuffer(query);
+ appendPQExpBufferStr(query, "SET statement_timeout = ");
+ appendStringLiteralConn(query, lockWaitTimeout, GetConnection(fout));
+ ExecuteSqlStatement(fout, query->data);
+ }
+
+ for (i = 0; i < ntups; i++)
+ {
+ tblinfo[i].dobj.objType = DO_TABLE;
+ tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid));
+ tblinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_reloid));
+ AssignDumpId(&tblinfo[i].dobj);
+ tblinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_relname));
+ tblinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_relnamespace)),
+ tblinfo[i].dobj.catId.oid);
+ tblinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ tblinfo[i].relacl = pg_strdup(PQgetvalue(res, i, i_relacl));
+ tblinfo[i].relkind = *(PQgetvalue(res, i, i_relkind));
+ tblinfo[i].relpersistence = *(PQgetvalue(res, i, i_relpersistence));
+ tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
+ tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
+ tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+ tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
+ tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
+ tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
+ tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
+ tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
+ tblinfo[i].minmxid = atooid(PQgetvalue(res, i, i_relminmxid));
+ tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
+ tblinfo[i].toast_frozenxid = atooid(PQgetvalue(res, i, i_toastfrozenxid));
+ tblinfo[i].toast_minmxid = atooid(PQgetvalue(res, i, i_toastminmxid));
+ if (PQgetisnull(res, i, i_reloftype))
+ tblinfo[i].reloftype = NULL;
+ else
+ tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+ tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
+ if (PQgetisnull(res, i, i_owning_tab))
+ {
+ tblinfo[i].owning_tab = InvalidOid;
+ tblinfo[i].owning_col = 0;
+ }
+ else
+ {
+ tblinfo[i].owning_tab = atooid(PQgetvalue(res, i, i_owning_tab));
+ tblinfo[i].owning_col = atoi(PQgetvalue(res, i, i_owning_col));
+ }
+ tblinfo[i].reltablespace = pg_strdup(PQgetvalue(res, i, i_reltablespace));
+ tblinfo[i].reloptions = pg_strdup(PQgetvalue(res, i, i_reloptions));
+ if (i_checkoption == -1 || PQgetisnull(res, i, i_checkoption))
+ tblinfo[i].checkoption = NULL;
+ else
+ tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
+ tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+
+ /* other fields were zeroed above */
+
+ /*
+ * Decide whether we want to dump this table.
+ */
+ if (tblinfo[i].relkind == RELKIND_COMPOSITE_TYPE)
+ tblinfo[i].dobj.dump = false;
+ else
+ selectDumpableTable(&tblinfo[i]);
+ tblinfo[i].interesting = tblinfo[i].dobj.dump;
+
+ tblinfo[i].postponed_def = false; /* might get set during sort */
+
+ /*
+ * Read-lock target tables to make sure they aren't DROPPED or altered
+ * in schema before we get around to dumping them.
+ *
+ * Note that we don't explicitly lock parents of the target tables; we
+ * assume our lock on the child is enough to prevent schema
+ * alterations to parent tables.
+ *
+ * NOTE: it'd be kinda nice to lock other relations too, not only
+ * plain tables, but the backend doesn't presently allow that.
+ */
+ if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
+ {
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query,
+ "LOCK TABLE %s IN ACCESS SHARE MODE",
+ fmtQualifiedId(fout->remoteVersion,
+ tblinfo[i].dobj.namespace->dobj.name,
+ tblinfo[i].dobj.name));
+ ExecuteSqlStatement(fout, query->data);
+ }
+
+ /* Emit notice if join for owner failed */
+ if (strlen(tblinfo[i].rolname) == 0)
+ write_msg(NULL, "WARNING: owner of table \"%s\" appears to be invalid\n",
+ tblinfo[i].dobj.name);
+ }
+
+ if (lockWaitTimeout && fout->remoteVersion >= 70300)
+ {
+ ExecuteSqlStatement(fout, "SET statement_timeout = 0");
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return tblinfo;
+}
+
+/*
+ * getOwnedSeqs
+ * identify owned sequences and mark them as dumpable if owning table is
+ *
+ * We used to do this in getTables(), but it's better to do it after the
+ * index used by findTableByOid() has been set up.
+ */
+void
+getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+ int i;
+
+ /*
+ * Force sequences that are "owned" by table columns to be dumped whenever
+ * their owning table is being dumped.
+ */
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *seqinfo = &tblinfo[i];
+ TableInfo *owning_tab;
+
+ if (!OidIsValid(seqinfo->owning_tab))
+ continue; /* not an owned sequence */
+ if (seqinfo->dobj.dump)
+ continue; /* no need to search */
+ owning_tab = findTableByOid(seqinfo->owning_tab);
+ if (owning_tab && owning_tab->dobj.dump)
+ {
+ seqinfo->interesting = true;
+ seqinfo->dobj.dump = true;
+ }
+ }
+}
+
+/*
+ * getInherits
+ * read all the inheritance information
+ * from the system catalogs return them in the InhInfo* structure
+ *
+ * numInherits is set to the number of pairs read in
+ */
+InhInfo *
+getInherits(Archive *fout, int *numInherits)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ InhInfo *inhinfo;
+
+ int i_inhrelid;
+ int i_inhparent;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numInherits = ntups;
+
+ inhinfo = (InhInfo *) pg_malloc(ntups * sizeof(InhInfo));
+
+ i_inhrelid = PQfnumber(res, "inhrelid");
+ i_inhparent = PQfnumber(res, "inhparent");
+
+ for (i = 0; i < ntups; i++)
+ {
+ inhinfo[i].inhrelid = atooid(PQgetvalue(res, i, i_inhrelid));
+ inhinfo[i].inhparent = atooid(PQgetvalue(res, i, i_inhparent));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return inhinfo;
+}
+
+/*
+ * getIndexes
+ * get information about every index on a dumpable table
+ *
+ * Note: index data is not returned directly to the caller, but it
+ * does get entered into the DumpableObject tables.
+ */
+void
+getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+ int i,
+ j;
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ IndxInfo *indxinfo;
+ ConstraintInfo *constrinfo;
+ int i_tableoid,
+ i_oid,
+ i_indexname,
+ i_indexdef,
+ i_indnkeys,
+ i_indkey,
+ i_indisclustered,
+ i_indisreplident,
+ i_contype,
+ i_conname,
+ i_condeferrable,
+ i_condeferred,
+ i_contableoid,
+ i_conoid,
+ i_condef,
+ i_tablespace,
+ i_options,
+ i_relpages;
+ int ntups;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &tblinfo[i];
+
+ /* Only plain tables and materialized views have indexes. */
+ if (tbinfo->relkind != RELKIND_RELATION &&
+ tbinfo->relkind != RELKIND_MATVIEW)
+ continue;
+ if (!tbinfo->hasindex)
+ continue;
+
+ /* Ignore indexes of tables not to be dumped */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ if (g_verbose)
+ write_msg(NULL, "reading indexes for table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ /* Make sure we are in proper schema so indexdef is right */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ /*
+ * 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.
+ */
+ resetPQExpBuffer(query);
+ 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, "
+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, i.indisclustered, "
+ "i.indisreplident, t.relpages, "
+ "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, "
+ "array_to_string(t.reloptions, ', ') AS options "
+ "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, "
+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, i.indisclustered, "
+ "false AS indisreplident, t.relpages, "
+ "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, "
+ "array_to_string(t.reloptions, ', ') AS options "
+ "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, "
+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, i.indisclustered, "
+ "false AS indisreplident, t.relpages, "
+ "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, "
+ "array_to_string(t.reloptions, ', ') AS options "
+ "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 if (fout->remoteVersion >= 80000)
+ {
+ appendPQExpBuffer(query,
+ "SELECT t.tableoid, t.oid, "
+ "t.relname AS indexname, "
+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, i.indisclustered, "
+ "false AS indisreplident, t.relpages, "
+ "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 options "
+ "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);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query,
+ "SELECT t.tableoid, t.oid, "
+ "t.relname AS indexname, "
+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, i.indisclustered, "
+ "false AS indisreplident, t.relpages, "
+ "c.contype, c.conname, "
+ "c.condeferrable, c.condeferred, "
+ "c.tableoid AS contableoid, "
+ "c.oid AS conoid, "
+ "null AS condef, "
+ "NULL AS tablespace, "
+ "null AS options "
+ "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);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query,
+ "SELECT t.tableoid, t.oid, "
+ "t.relname AS indexname, "
+ "pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, false AS indisclustered, "
+ "false AS indisreplident, t.relpages, "
+ "CASE WHEN i.indisprimary THEN 'p'::char "
+ "ELSE '0'::char END AS contype, "
+ "t.relname AS conname, "
+ "false AS condeferrable, "
+ "false AS condeferred, "
+ "0::oid AS contableoid, "
+ "t.oid AS conoid, "
+ "null AS condef, "
+ "NULL AS tablespace, "
+ "null AS options "
+ "FROM pg_index i, pg_class t "
+ "WHERE t.oid = i.indexrelid "
+ "AND i.indrelid = '%u'::oid "
+ "ORDER BY indexname",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query,
+ "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_class') AS tableoid, "
+ "t.oid, "
+ "t.relname AS indexname, "
+ "pg_get_indexdef(i.indexrelid) AS indexdef, "
+ "t.relnatts AS indnkeys, "
+ "i.indkey, false AS indisclustered, "
+ "false AS indisreplident, t.relpages, "
+ "CASE WHEN i.indisprimary THEN 'p'::char "
+ "ELSE '0'::char END AS contype, "
+ "t.relname AS conname, "
+ "false AS condeferrable, "
+ "false AS condeferred, "
+ "0::oid AS contableoid, "
+ "t.oid AS conoid, "
+ "null AS condef, "
+ "NULL AS tablespace, "
+ "null AS options "
+ "FROM pg_index i, pg_class t "
+ "WHERE t.oid = i.indexrelid "
+ "AND i.indrelid = '%u'::oid "
+ "ORDER BY indexname",
+ tbinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_indexname = PQfnumber(res, "indexname");
+ i_indexdef = PQfnumber(res, "indexdef");
+ i_indnkeys = PQfnumber(res, "indnkeys");
+ i_indkey = PQfnumber(res, "indkey");
+ i_indisclustered = PQfnumber(res, "indisclustered");
+ i_indisreplident = PQfnumber(res, "indisreplident");
+ i_relpages = PQfnumber(res, "relpages");
+ 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_options = PQfnumber(res, "options");
+
+ indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
+ constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ char contype;
+
+ indxinfo[j].dobj.objType = DO_INDEX;
+ indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
+ indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&indxinfo[j].dobj);
+ indxinfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_indexname));
+ indxinfo[j].dobj.namespace = tbinfo->dobj.namespace;
+ indxinfo[j].indextable = tbinfo;
+ indxinfo[j].indexdef = pg_strdup(PQgetvalue(res, j, i_indexdef));
+ indxinfo[j].indnkeys = atoi(PQgetvalue(res, j, i_indnkeys));
+ indxinfo[j].tablespace = pg_strdup(PQgetvalue(res, j, i_tablespace));
+ indxinfo[j].options = pg_strdup(PQgetvalue(res, j, i_options));
+
+ /*
+ * In pre-7.4 releases, indkeys may contain more entries than
+ * indnkeys says (since indnkeys will be 1 for a functional
+ * index). We don't actually care about this case since we don't
+ * examine indkeys except for indexes associated with PRIMARY and
+ * UNIQUE constraints, which are never functional indexes. But we
+ * have to allocate enough space to keep parseOidArray from
+ * complaining.
+ */
+ indxinfo[j].indkeys = (Oid *) pg_malloc(INDEX_MAX_KEYS * sizeof(Oid));
+ parseOidArray(PQgetvalue(res, j, i_indkey),
+ indxinfo[j].indkeys, INDEX_MAX_KEYS);
+ indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
+ indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
+ indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
+ contype = *(PQgetvalue(res, j, i_contype));
+
+ if (contype == 'p' || contype == 'u' || contype == 'x')
+ {
+ /*
+ * If we found a constraint matching the index, create an
+ * entry for it.
+ *
+ * In a pre-7.3 database, we take this path iff the index was
+ * marked indisprimary.
+ */
+ constrinfo[j].dobj.objType = DO_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 = contype;
+ if (contype == 'x')
+ constrinfo[j].condef = pg_strdup(PQgetvalue(res, j, i_condef));
+ else
+ constrinfo[j].condef = NULL;
+ constrinfo[j].confrelid = InvalidOid;
+ constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
+ constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
+ constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
+ constrinfo[j].conislocal = true;
+ constrinfo[j].separate = true;
+
+ indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
+
+ /* If pre-7.3 DB, better make sure table comes first */
+ addObjectDependency(&constrinfo[j].dobj,
+ tbinfo->dobj.dumpId);
+ }
+ else
+ {
+ /* Plain secondary index */
+ indxinfo[j].indexconstraint = 0;
+ }
+ }
+
+ PQclear(res);
+ }
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getConstraints
+ *
+ * Get info about constraints on dumpable tables.
+ *
+ * Currently handles foreign keys only.
+ * Unique and primary key constraints are handled with indexes,
+ * while check constraints are processed in getTableAttrs().
+ */
+void
+getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+ int i,
+ j;
+ ConstraintInfo *constrinfo;
+ PQExpBuffer query;
+ PGresult *res;
+ int i_contableoid,
+ i_conoid,
+ i_conname,
+ i_confrelid,
+ i_condef;
+ int ntups;
+
+ /* pg_constraint was created in 7.3, so nothing to do if older */
+ if (fout->remoteVersion < 70300)
+ return;
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &tblinfo[i];
+
+ if (!tbinfo->hastriggers || !tbinfo->dobj.dump)
+ continue;
+
+ if (g_verbose)
+ write_msg(NULL, "reading foreign key constraints for table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ /*
+ * select table schema to ensure constraint expr is qualified if
+ * needed
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query,
+ "SELECT tableoid, oid, conname, confrelid, "
+ "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);
+
+ ntups = PQntuples(res);
+
+ i_contableoid = PQfnumber(res, "tableoid");
+ i_conoid = PQfnumber(res, "oid");
+ i_conname = PQfnumber(res, "conname");
+ i_confrelid = PQfnumber(res, "confrelid");
+ i_condef = PQfnumber(res, "condef");
+
+ constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ 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;
+ }
+
+ PQclear(res);
+ }
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getDomainConstraints
+ *
+ * Get info about constraints on a domain.
+ */
+static void
+getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
+{
+ int i;
+ ConstraintInfo *constrinfo;
+ PQExpBuffer query;
+ PGresult *res;
+ int i_tableoid,
+ i_oid,
+ i_conname,
+ i_consrc;
+ int ntups;
+
+ /* pg_constraint was created in 7.3, so nothing to do if older */
+ if (fout->remoteVersion < 70300)
+ return;
+
+ /*
+ * select appropriate schema to ensure names in constraint are properly
+ * qualified
+ */
+ selectSourceSchema(fout, tyinfo->dobj.namespace->dobj.name);
+
+ query = createPQExpBuffer();
+
+ 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);
+
+ else if (fout->remoteVersion >= 70400)
+ 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);
+ else
+ appendPQExpBuffer(query, "SELECT tableoid, oid, conname, "
+ "'CHECK (' || consrc || ')' AS consrc, "
+ "true as convalidated "
+ "FROM pg_catalog.pg_constraint "
+ "WHERE contypid = '%u'::pg_catalog.oid "
+ "ORDER BY conname",
+ tyinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_conname = PQfnumber(res, "conname");
+ i_consrc = PQfnumber(res, "consrc");
+
+ constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
+
+ tyinfo->nDomChecks = ntups;
+ tyinfo->domChecks = constrinfo;
+
+ for (i = 0; i < ntups; i++)
+ {
+ bool validated = PQgetvalue(res, i, 4)[0] == 't';
+
+ constrinfo[i].dobj.objType = DO_CONSTRAINT;
+ constrinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ constrinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&constrinfo[i].dobj);
+ constrinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_conname));
+ constrinfo[i].dobj.namespace = tyinfo->dobj.namespace;
+ constrinfo[i].contable = NULL;
+ constrinfo[i].condomain = tyinfo;
+ constrinfo[i].contype = 'c';
+ constrinfo[i].condef = pg_strdup(PQgetvalue(res, i, i_consrc));
+ constrinfo[i].confrelid = InvalidOid;
+ constrinfo[i].conindex = 0;
+ constrinfo[i].condeferrable = false;
+ constrinfo[i].condeferred = false;
+ constrinfo[i].conislocal = true;
+
+ constrinfo[i].separate = !validated;
+
+ /*
+ * Make the domain depend on the constraint, ensuring it won't be
+ * output till any constraint dependencies are OK. If the constraint
+ * has not been validated, it's going to be dumped after the domain
+ * anyway, so this doesn't matter.
+ */
+ if (validated)
+ addObjectDependency(&tyinfo->dobj,
+ constrinfo[i].dobj.dumpId);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getRules
+ * get basic information about every rule in the system
+ *
+ * numRules is set to the number of rules read in
+ */
+RuleInfo *
+getRules(Archive *fout, int *numRules)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ RuleInfo *ruleinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_rulename;
+ int i_ruletable;
+ int i_ev_type;
+ int i_is_instead;
+ int i_ev_enabled;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBufferStr(query, "SELECT "
+ "tableoid, oid, rulename, "
+ "ev_class AS ruletable, ev_type, is_instead, "
+ "ev_enabled "
+ "FROM pg_rewrite "
+ "ORDER BY oid");
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBufferStr(query, "SELECT "
+ "tableoid, oid, rulename, "
+ "ev_class AS ruletable, ev_type, is_instead, "
+ "'O'::char AS ev_enabled "
+ "FROM pg_rewrite "
+ "ORDER BY oid");
+ }
+ else
+ {
+ appendPQExpBufferStr(query, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_rewrite') AS tableoid, "
+ "oid, rulename, "
+ "ev_class AS ruletable, ev_type, is_instead, "
+ "'O'::char AS ev_enabled "
+ "FROM pg_rewrite "
+ "ORDER BY oid");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numRules = ntups;
+
+ ruleinfo = (RuleInfo *) pg_malloc(ntups * sizeof(RuleInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_rulename = PQfnumber(res, "rulename");
+ i_ruletable = PQfnumber(res, "ruletable");
+ i_ev_type = PQfnumber(res, "ev_type");
+ i_is_instead = PQfnumber(res, "is_instead");
+ i_ev_enabled = PQfnumber(res, "ev_enabled");
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid ruletableoid;
+
+ ruleinfo[i].dobj.objType = DO_RULE;
+ ruleinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ ruleinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&ruleinfo[i].dobj);
+ ruleinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_rulename));
+ ruletableoid = atooid(PQgetvalue(res, i, i_ruletable));
+ ruleinfo[i].ruletable = findTableByOid(ruletableoid);
+ if (ruleinfo[i].ruletable == NULL)
+ exit_horribly(NULL, "failed sanity check, parent table OID %u of pg_rewrite entry OID %u not found\n",
+ ruletableoid, ruleinfo[i].dobj.catId.oid);
+ ruleinfo[i].dobj.namespace = ruleinfo[i].ruletable->dobj.namespace;
+ ruleinfo[i].dobj.dump = ruleinfo[i].ruletable->dobj.dump;
+ ruleinfo[i].ev_type = *(PQgetvalue(res, i, i_ev_type));
+ ruleinfo[i].is_instead = *(PQgetvalue(res, i, i_is_instead)) == 't';
+ ruleinfo[i].ev_enabled = *(PQgetvalue(res, i, i_ev_enabled));
+ if (ruleinfo[i].ruletable)
+ {
+ /*
+ * If the table is a view or materialized view, force its ON
+ * SELECT rule to be sorted before the view itself --- this
+ * ensures that any dependencies for the rule affect the table's
+ * positioning. Other rules are forced to appear after their
+ * table.
+ */
+ if ((ruleinfo[i].ruletable->relkind == RELKIND_VIEW ||
+ ruleinfo[i].ruletable->relkind == RELKIND_MATVIEW) &&
+ ruleinfo[i].ev_type == '1' && ruleinfo[i].is_instead)
+ {
+ addObjectDependency(&ruleinfo[i].ruletable->dobj,
+ ruleinfo[i].dobj.dumpId);
+ /* We'll merge the rule into CREATE VIEW, if possible */
+ ruleinfo[i].separate = false;
+ }
+ else
+ {
+ addObjectDependency(&ruleinfo[i].dobj,
+ ruleinfo[i].ruletable->dobj.dumpId);
+ ruleinfo[i].separate = true;
+ }
+ }
+ else
+ ruleinfo[i].separate = true;
+
+ /*
+ * If we're forced to break a dependency loop by dumping a view as a
+ * table and separate _RETURN rule, we'll move the view's reloptions
+ * to the rule. (This is necessary because tables and views have
+ * different valid reloptions, so we can't apply the options until the
+ * backend knows it's a view.) Otherwise the rule's reloptions stay
+ * NULL.
+ */
+ ruleinfo[i].reloptions = NULL;
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return ruleinfo;
+}
+
+/*
+ * getTriggers
+ * get information about every trigger on a dumpable table
+ *
+ * Note: trigger data is not returned directly to the caller, but it
+ * does get entered into the DumpableObject tables.
+ */
+void
+getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+ int i,
+ j;
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ TriggerInfo *tginfo;
+ int i_tableoid,
+ i_oid,
+ i_tgname,
+ i_tgfname,
+ i_tgtype,
+ i_tgnargs,
+ i_tgargs,
+ i_tgisconstraint,
+ i_tgconstrname,
+ i_tgconstrrelid,
+ i_tgconstrrelname,
+ i_tgenabled,
+ i_tgdeferrable,
+ i_tginitdeferred,
+ i_tgdef;
+ int ntups;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &tblinfo[i];
+
+ if (!tbinfo->hastriggers || !tbinfo->dobj.dump)
+ continue;
+
+ if (g_verbose)
+ write_msg(NULL, "reading triggers for table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ /*
+ * select table schema to ensure regproc name is qualified if needed
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ resetPQExpBuffer(query);
+ if (fout->remoteVersion >= 90000)
+ {
+ /*
+ * 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.
+ */
+ appendPQExpBuffer(query,
+ "SELECT tgname, "
+ "tgfoid::pg_catalog.regproc AS tgfname, "
+ "pg_catalog.pg_get_triggerdef(oid, false) AS tgdef, "
+ "tgenabled, tableoid, 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, "
+ "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 if (fout->remoteVersion >= 70300)
+ {
+ /*
+ * 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, "
+ "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);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query,
+ "SELECT tgname, tgfoid::regproc AS tgfname, "
+ "tgtype, tgnargs, tgargs, tgenabled, "
+ "tgisconstraint, tgconstrname, tgdeferrable, "
+ "tgconstrrelid, tginitdeferred, tableoid, oid, "
+ "(SELECT relname FROM pg_class WHERE oid = tgconstrrelid) "
+ " AS tgconstrrelname "
+ "FROM pg_trigger "
+ "WHERE tgrelid = '%u'::oid",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query,
+ "SELECT tgname, tgfoid::regproc AS tgfname, "
+ "tgtype, tgnargs, tgargs, tgenabled, "
+ "tgisconstraint, tgconstrname, tgdeferrable, "
+ "tgconstrrelid, tginitdeferred, "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_trigger') AS tableoid, "
+ "oid, "
+ "(SELECT relname FROM pg_class WHERE oid = tgconstrrelid) "
+ " AS tgconstrrelname "
+ "FROM pg_trigger "
+ "WHERE tgrelid = '%u'::oid",
+ tbinfo->dobj.catId.oid);
+ }
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ 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_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++)
+ {
+ tginfo[j].dobj.objType = DO_TRIGGER;
+ tginfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
+ tginfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&tginfo[j].dobj);
+ tginfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_tgname));
+ tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
+ tginfo[j].tgtable = tbinfo;
+ tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
+ if (i_tgdef >= 0)
+ {
+ tginfo[j].tgdef = pg_strdup(PQgetvalue(res, j, i_tgdef));
+
+ /* remaining fields are not valid if we have tgdef */
+ tginfo[j].tgfname = NULL;
+ tginfo[j].tgtype = 0;
+ tginfo[j].tgnargs = 0;
+ tginfo[j].tgargs = NULL;
+ tginfo[j].tgisconstraint = false;
+ tginfo[j].tgdeferrable = false;
+ tginfo[j].tginitdeferred = false;
+ tginfo[j].tgconstrname = NULL;
+ tginfo[j].tgconstrrelid = InvalidOid;
+ tginfo[j].tgconstrrelname = NULL;
+ }
+ else
+ {
+ tginfo[j].tgdef = NULL;
+
+ tginfo[j].tgfname = pg_strdup(PQgetvalue(res, j, i_tgfname));
+ tginfo[j].tgtype = atoi(PQgetvalue(res, j, i_tgtype));
+ tginfo[j].tgnargs = atoi(PQgetvalue(res, j, i_tgnargs));
+ tginfo[j].tgargs = pg_strdup(PQgetvalue(res, j, i_tgargs));
+ tginfo[j].tgisconstraint = *(PQgetvalue(res, j, i_tgisconstraint)) == 't';
+ tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
+ tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
+
+ if (tginfo[j].tgisconstraint)
+ {
+ tginfo[j].tgconstrname = pg_strdup(PQgetvalue(res, j, i_tgconstrname));
+ tginfo[j].tgconstrrelid = atooid(PQgetvalue(res, j, i_tgconstrrelid));
+ if (OidIsValid(tginfo[j].tgconstrrelid))
+ {
+ if (PQgetisnull(res, j, i_tgconstrrelname))
+ exit_horribly(NULL, "query produced null referenced table name for foreign key trigger \"%s\" on table \"%s\" (OID of table: %u)\n",
+ tginfo[j].dobj.name,
+ tbinfo->dobj.name,
+ tginfo[j].tgconstrrelid);
+ tginfo[j].tgconstrrelname = pg_strdup(PQgetvalue(res, j, i_tgconstrrelname));
+ }
+ else
+ tginfo[j].tgconstrrelname = NULL;
+ }
+ else
+ {
+ tginfo[j].tgconstrname = NULL;
+ tginfo[j].tgconstrrelid = InvalidOid;
+ tginfo[j].tgconstrrelname = NULL;
+ }
+ }
+ }
+
+ PQclear(res);
+ }
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getEventTriggers
+ * get information about event triggers
+ */
+EventTriggerInfo *
+getEventTriggers(Archive *fout, int *numEventTriggers)
+{
+ int i;
+ PQExpBuffer query;
+ PGresult *res;
+ EventTriggerInfo *evtinfo;
+ int i_tableoid,
+ i_oid,
+ i_evtname,
+ i_evtevent,
+ i_evtowner,
+ i_evttags,
+ i_evtfname,
+ i_evtenabled;
+ int ntups;
+
+ /* Before 9.3, there are no event triggers */
+ if (fout->remoteVersion < 90300)
+ {
+ *numEventTriggers = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query,
+ "SELECT e.tableoid, e.oid, evtname, evtenabled, "
+ "evtevent, (%s evtowner) AS evtowner, "
+ "array_to_string(array("
+ "select quote_literal(x) "
+ " from unnest(evttags) as t(x)), ', ') as evttags, "
+ "e.evtfoid::regproc as evtfname "
+ "FROM pg_event_trigger e "
+ "ORDER BY e.oid",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numEventTriggers = ntups;
+
+ evtinfo = (EventTriggerInfo *) pg_malloc(ntups * sizeof(EventTriggerInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_evtname = PQfnumber(res, "evtname");
+ i_evtevent = PQfnumber(res, "evtevent");
+ i_evtowner = PQfnumber(res, "evtowner");
+ i_evttags = PQfnumber(res, "evttags");
+ i_evtfname = PQfnumber(res, "evtfname");
+ i_evtenabled = PQfnumber(res, "evtenabled");
+
+ for (i = 0; i < ntups; i++)
+ {
+ evtinfo[i].dobj.objType = DO_EVENT_TRIGGER;
+ evtinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ evtinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&evtinfo[i].dobj);
+ evtinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_evtname));
+ evtinfo[i].evtname = pg_strdup(PQgetvalue(res, i, i_evtname));
+ evtinfo[i].evtevent = pg_strdup(PQgetvalue(res, i, i_evtevent));
+ evtinfo[i].evtowner = pg_strdup(PQgetvalue(res, i, i_evtowner));
+ evtinfo[i].evttags = pg_strdup(PQgetvalue(res, i, i_evttags));
+ evtinfo[i].evtfname = pg_strdup(PQgetvalue(res, i, i_evtfname));
+ evtinfo[i].evtenabled = *(PQgetvalue(res, i, i_evtenabled));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return evtinfo;
+}
+
+/*
+ * getProcLangs
+ * get basic information about every procedural language in the system
+ *
+ * numProcLangs is set to the number of langs read in
+ *
+ * NB: this must run after getFuncs() because we assume we can do
+ * findFuncByOid().
+ */
+ProcLangInfo *
+getProcLangs(Archive *fout, int *numProcLangs)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ ProcLangInfo *planginfo;
+ int i_tableoid;
+ int i_oid;
+ int i_lanname;
+ int i_lanpltrusted;
+ int i_lanplcallfoid;
+ int i_laninline;
+ int i_lanvalidator;
+ int i_lanacl;
+ int i_lanowner;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 90000)
+ {
+ /* pg_language has a laninline column */
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "laninline, lanvalidator, lanacl, "
+ "(%s lanowner) AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 80300)
+ {
+ /* pg_language has a lanowner column */
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "0 AS laninline, lanvalidator, lanacl, "
+ "(%s lanowner) AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 80100)
+ {
+ /* Languages are owned by the bootstrap superuser, OID 10 */
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "0 AS laninline, lanvalidator, lanacl, "
+ "(%s '10') AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70400)
+ {
+ /* Languages are owned by the bootstrap superuser, sysid 1 */
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "0 AS laninline, lanvalidator, lanacl, "
+ "(%s '1') AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid",
+ username_subquery);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ /* No clear notion of an owner at all before 7.4 ... */
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "0 AS laninline, lanvalidator, lanacl, "
+ "NULL AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid");
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "0 AS laninline, 0 AS lanvalidator, NULL AS lanacl, "
+ "NULL AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid");
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_language') AS tableoid, "
+ "oid, "
+ "lanname, lanpltrusted, lanplcallfoid, "
+ "0 AS laninline, 0 AS lanvalidator, NULL AS lanacl, "
+ "NULL AS lanowner "
+ "FROM pg_language "
+ "WHERE lanispl "
+ "ORDER BY oid");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numProcLangs = ntups;
+
+ planginfo = (ProcLangInfo *) pg_malloc(ntups * sizeof(ProcLangInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_lanname = PQfnumber(res, "lanname");
+ i_lanpltrusted = PQfnumber(res, "lanpltrusted");
+ i_lanplcallfoid = PQfnumber(res, "lanplcallfoid");
+ i_laninline = PQfnumber(res, "laninline");
+ i_lanvalidator = PQfnumber(res, "lanvalidator");
+ i_lanacl = PQfnumber(res, "lanacl");
+ i_lanowner = PQfnumber(res, "lanowner");
+
+ for (i = 0; i < ntups; i++)
+ {
+ planginfo[i].dobj.objType = DO_PROCLANG;
+ planginfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ planginfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&planginfo[i].dobj);
+
+ planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname));
+ 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].lanowner = pg_strdup(PQgetvalue(res, i, i_lanowner));
+
+ if (fout->remoteVersion < 70300)
+ {
+ /*
+ * We need to make a dependency to ensure the function will be
+ * dumped first. (In 7.3 and later the regular dependency
+ * mechanism will handle this for us.)
+ */
+ FuncInfo *funcInfo = findFuncByOid(planginfo[i].lanplcallfoid);
+
+ if (funcInfo)
+ addObjectDependency(&planginfo[i].dobj,
+ funcInfo->dobj.dumpId);
+ }
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return planginfo;
+}
+
+/*
+ * getCasts
+ * get basic information about every cast in the system
+ *
+ * numCasts is set to the number of casts read in
+ */
+CastInfo *
+getCasts(Archive *fout, int *numCasts)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ CastInfo *castinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_castsource;
+ int i_casttarget;
+ int i_castfunc;
+ int i_castcontext;
+ int i_castmethod;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+ "castsource, casttarget, castfunc, castcontext, "
+ "castmethod "
+ "FROM pg_cast ORDER BY 3,4");
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+ "castsource, casttarget, castfunc, castcontext, "
+ "CASE WHEN castfunc = 0 THEN 'b' ELSE 'f' END AS castmethod "
+ "FROM pg_cast ORDER BY 3,4");
+ }
+ else
+ {
+ appendPQExpBufferStr(query, "SELECT 0 AS tableoid, p.oid, "
+ "t1.oid AS castsource, t2.oid AS casttarget, "
+ "p.oid AS castfunc, 'e' AS castcontext, "
+ "'f' AS castmethod "
+ "FROM pg_type t1, pg_type t2, pg_proc p "
+ "WHERE p.pronargs = 1 AND "
+ "p.proargtypes[0] = t1.oid AND "
+ "p.prorettype = t2.oid AND p.proname = t2.typname "
+ "ORDER BY 3,4");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numCasts = ntups;
+
+ castinfo = (CastInfo *) pg_malloc(ntups * sizeof(CastInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_castsource = PQfnumber(res, "castsource");
+ i_casttarget = PQfnumber(res, "casttarget");
+ i_castfunc = PQfnumber(res, "castfunc");
+ i_castcontext = PQfnumber(res, "castcontext");
+ i_castmethod = PQfnumber(res, "castmethod");
+
+ for (i = 0; i < ntups; i++)
+ {
+ PQExpBufferData namebuf;
+ TypeInfo *sTypeInfo;
+ TypeInfo *tTypeInfo;
+
+ castinfo[i].dobj.objType = DO_CAST;
+ castinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ castinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&castinfo[i].dobj);
+ castinfo[i].castsource = atooid(PQgetvalue(res, i, i_castsource));
+ castinfo[i].casttarget = atooid(PQgetvalue(res, i, i_casttarget));
+ castinfo[i].castfunc = atooid(PQgetvalue(res, i, i_castfunc));
+ castinfo[i].castcontext = *(PQgetvalue(res, i, i_castcontext));
+ castinfo[i].castmethod = *(PQgetvalue(res, i, i_castmethod));
+
+ /*
+ * Try to name cast as concatenation of typnames. This is only used
+ * for purposes of sorting. If we fail to find either type, the name
+ * will be an empty string.
+ */
+ initPQExpBuffer(&namebuf);
+ sTypeInfo = findTypeByOid(castinfo[i].castsource);
+ tTypeInfo = findTypeByOid(castinfo[i].casttarget);
+ if (sTypeInfo && tTypeInfo)
+ appendPQExpBuffer(&namebuf, "%s %s",
+ sTypeInfo->dobj.name, tTypeInfo->dobj.name);
+ castinfo[i].dobj.name = namebuf.data;
+
+ if (fout->remoteVersion < 70300 &&
+ OidIsValid(castinfo[i].castfunc))
+ {
+ /*
+ * We need to make a dependency to ensure the function will be
+ * dumped first. (In 7.3 and later the regular dependency
+ * mechanism handles this for us.)
+ */
+ FuncInfo *funcInfo;
+
+ funcInfo = findFuncByOid(castinfo[i].castfunc);
+ if (funcInfo)
+ addObjectDependency(&castinfo[i].dobj,
+ funcInfo->dobj.dumpId);
+ }
+
+ /* Decide whether we want to dump it */
+ selectDumpableCast(&(castinfo[i]));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return castinfo;
+}
+
+/*
+ * getTableAttrs -
+ * 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
+getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+ int i,
+ j;
+ PQExpBuffer q = createPQExpBuffer();
+ int i_attnum;
+ int i_attname;
+ int i_atttypname;
+ int i_atttypmod;
+ int i_attstattarget;
+ int i_attstorage;
+ int i_typstorage;
+ int i_attnotnull;
+ int i_atthasdef;
+ int i_attisdropped;
+ int i_attlen;
+ int i_attalign;
+ int i_attislocal;
+ int i_attoptions;
+ int i_attcollation;
+ int i_attfdwoptions;
+ PGresult *res;
+ int ntups;
+ bool hasdefaults;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &tblinfo[i];
+
+ /* Don't bother to collect info for sequences */
+ if (tbinfo->relkind == RELKIND_SEQUENCE)
+ continue;
+
+ /* Don't bother with uninteresting tables, either */
+ if (!tbinfo->interesting)
+ continue;
+
+ /*
+ * Make sure we are in proper schema for this table; this allows
+ * correct retrieval of formatted type names and default exprs
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ /* find all the user attributes and their types */
+
+ /*
+ * we must read the attribute names in attribute number order! because
+ * we will use the attnum to index into the attnames array later. We
+ * actually ask to order by "attrelid, attnum" because (at least up to
+ * 7.3) the planner is not smart enough to realize it needn't re-sort
+ * the output of an indexscan on pg_attribute_relid_attnum_index.
+ */
+ if (g_verbose)
+ write_msg(NULL, "finding the columns and types of table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ resetPQExpBuffer(q);
+
+ if (fout->remoteVersion >= 90200)
+ {
+ /*
+ * attfdwoptions is new in 9.2.
+ */
+ appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, "
+ "a.attstattarget, a.attstorage, t.typstorage, "
+ "a.attnotnull, a.atthasdef, a.attisdropped, "
+ "a.attlen, a.attalign, a.attislocal, "
+ "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
+ "array_to_string(a.attoptions, ', ') AS attoptions, "
+ "CASE WHEN a.attcollation <> t.typcollation "
+ "THEN a.attcollation ELSE 0 END AS attcollation, "
+ "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 "
+ "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
+ "ON a.atttypid = t.oid "
+ "WHERE a.attrelid = '%u'::pg_catalog.oid "
+ "AND a.attnum > 0::pg_catalog.int2 "
+ "ORDER BY a.attrelid, a.attnum",
+ tbinfo->dobj.catId.oid);
+ }
+ else 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.
+ */
+ appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, "
+ "a.attstattarget, a.attstorage, t.typstorage, "
+ "a.attnotnull, a.atthasdef, a.attisdropped, "
+ "a.attlen, a.attalign, a.attislocal, "
+ "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
+ "array_to_string(a.attoptions, ', ') AS attoptions, "
+ "CASE WHEN a.attcollation <> t.typcollation "
+ "THEN a.attcollation ELSE 0 END AS attcollation, "
+ "NULL AS attfdwoptions "
+ "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
+ "ON a.atttypid = t.oid "
+ "WHERE a.attrelid = '%u'::pg_catalog.oid "
+ "AND a.attnum > 0::pg_catalog.int2 "
+ "ORDER BY a.attrelid, a.attnum",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 90000)
+ {
+ /* attoptions is new in 9.0 */
+ appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, "
+ "a.attstattarget, a.attstorage, t.typstorage, "
+ "a.attnotnull, a.atthasdef, a.attisdropped, "
+ "a.attlen, a.attalign, a.attislocal, "
+ "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
+ "array_to_string(a.attoptions, ', ') AS attoptions, "
+ "0 AS attcollation, "
+ "NULL AS attfdwoptions "
+ "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
+ "ON a.atttypid = t.oid "
+ "WHERE a.attrelid = '%u'::pg_catalog.oid "
+ "AND a.attnum > 0::pg_catalog.int2 "
+ "ORDER BY a.attrelid, a.attnum",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ /* need left join here to not fail on dropped columns ... */
+ appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, "
+ "a.attstattarget, a.attstorage, t.typstorage, "
+ "a.attnotnull, a.atthasdef, a.attisdropped, "
+ "a.attlen, a.attalign, a.attislocal, "
+ "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
+ "'' AS attoptions, 0 AS attcollation, "
+ "NULL AS attfdwoptions "
+ "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
+ "ON a.atttypid = t.oid "
+ "WHERE a.attrelid = '%u'::pg_catalog.oid "
+ "AND a.attnum > 0::pg_catalog.int2 "
+ "ORDER BY a.attrelid, a.attnum",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ /*
+ * attstattarget doesn't exist in 7.1. It does exist in 7.2, but
+ * we don't dump it because we can't tell whether it's been
+ * explicitly set or was just a default.
+ *
+ * attislocal doesn't exist before 7.3, either; in older databases
+ * we assume it's TRUE, else we'd fail to dump non-inherited atts.
+ */
+ appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, "
+ "-1 AS attstattarget, a.attstorage, "
+ "t.typstorage, a.attnotnull, a.atthasdef, "
+ "false AS attisdropped, a.attlen, "
+ "a.attalign, true AS attislocal, "
+ "format_type(t.oid,a.atttypmod) AS atttypname, "
+ "'' AS attoptions, 0 AS attcollation, "
+ "NULL AS attfdwoptions "
+ "FROM pg_attribute a LEFT JOIN pg_type t "
+ "ON a.atttypid = t.oid "
+ "WHERE a.attrelid = '%u'::oid "
+ "AND a.attnum > 0::int2 "
+ "ORDER BY a.attrelid, a.attnum",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ /* format_type not available before 7.1 */
+ appendPQExpBuffer(q, "SELECT attnum, attname, atttypmod, "
+ "-1 AS attstattarget, "
+ "attstorage, attstorage AS typstorage, "
+ "attnotnull, atthasdef, false AS attisdropped, "
+ "attlen, attalign, "
+ "true AS attislocal, "
+ "(SELECT typname FROM pg_type WHERE oid = atttypid) AS atttypname, "
+ "'' AS attoptions, 0 AS attcollation, "
+ "NULL AS attfdwoptions "
+ "FROM pg_attribute a "
+ "WHERE attrelid = '%u'::oid "
+ "AND attnum > 0::int2 "
+ "ORDER BY attrelid, attnum",
+ tbinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ 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_attnotnull = PQfnumber(res, "attnotnull");
+ i_atthasdef = PQfnumber(res, "atthasdef");
+ i_attisdropped = PQfnumber(res, "attisdropped");
+ i_attlen = PQfnumber(res, "attlen");
+ i_attalign = PQfnumber(res, "attalign");
+ i_attislocal = PQfnumber(res, "attislocal");
+ i_attoptions = PQfnumber(res, "attoptions");
+ i_attcollation = PQfnumber(res, "attcollation");
+ i_attfdwoptions = PQfnumber(res, "attfdwoptions");
+
+ 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->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->attfdwoptions = (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 *));
+ hasdefaults = false;
+
+ for (j = 0; j < ntups; j++)
+ {
+ if (j + 1 != atoi(PQgetvalue(res, j, i_attnum)))
+ exit_horribly(NULL,
+ "invalid column numbering in table \"%s\"\n",
+ 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->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->attfdwoptions[j] = pg_strdup(PQgetvalue(res, j, i_attfdwoptions));
+ tbinfo->attrdefs[j] = NULL; /* fix below */
+ if (PQgetvalue(res, j, i_atthasdef)[0] == 't')
+ hasdefaults = true;
+ /* these flags will be set in flagInhAttrs() */
+ tbinfo->inhNotNull[j] = false;
+ }
+
+ PQclear(res);
+
+ /*
+ * Get info about column defaults
+ */
+ if (hasdefaults)
+ {
+ AttrDefInfo *attrdefs;
+ int numDefaults;
+
+ if (g_verbose)
+ write_msg(NULL, "finding default expressions of table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ resetPQExpBuffer(q);
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(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);
+ }
+ else if (fout->remoteVersion >= 70200)
+ {
+ /* 7.2 did not have OIDs in pg_attrdef */
+ appendPQExpBuffer(q, "SELECT tableoid, 0 AS oid, adnum, "
+ "pg_get_expr(adbin, adrelid) AS adsrc "
+ "FROM pg_attrdef "
+ "WHERE adrelid = '%u'::oid",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ /* no pg_get_expr, so must rely on adsrc */
+ appendPQExpBuffer(q, "SELECT tableoid, oid, adnum, adsrc "
+ "FROM pg_attrdef "
+ "WHERE adrelid = '%u'::oid",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ /* no pg_get_expr, no tableoid either */
+ appendPQExpBuffer(q, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_attrdef') AS tableoid, "
+ "oid, adnum, adsrc "
+ "FROM pg_attrdef "
+ "WHERE adrelid = '%u'::oid",
+ tbinfo->dobj.catId.oid);
+ }
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+
+ numDefaults = PQntuples(res);
+ attrdefs = (AttrDefInfo *) pg_malloc(numDefaults * sizeof(AttrDefInfo));
+
+ for (j = 0; j < numDefaults; j++)
+ {
+ int adnum;
+
+ adnum = atoi(PQgetvalue(res, j, 2));
+
+ if (adnum <= 0 || adnum > ntups)
+ exit_horribly(NULL,
+ "invalid adnum value %d for table \"%s\"\n",
+ adnum, tbinfo->dobj.name);
+
+ /*
+ * dropped columns shouldn't have defaults, but just in case,
+ * ignore 'em
+ */
+ if (tbinfo->attisdropped[adnum - 1])
+ continue;
+
+ 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));
+
+ attrdefs[j].dobj.name = pg_strdup(tbinfo->dobj.name);
+ attrdefs[j].dobj.namespace = tbinfo->dobj.namespace;
+
+ attrdefs[j].dobj.dump = tbinfo->dobj.dump;
+
+ /*
+ * Defaults on a VIEW must always be dumped as separate ALTER
+ * TABLE commands. Defaults on regular tables are dumped as
+ * part of the CREATE TABLE if possible, which it won't be if
+ * the column is not going to be emitted explicitly.
+ */
+ if (tbinfo->relkind == RELKIND_VIEW)
+ {
+ attrdefs[j].separate = true;
+ /* needed in case pre-7.3 DB: */
+ addObjectDependency(&attrdefs[j].dobj,
+ tbinfo->dobj.dumpId);
+ }
+ else if (!shouldPrintColumn(tbinfo, adnum - 1))
+ {
+ /* column will be suppressed, print default separately */
+ attrdefs[j].separate = true;
+ /* needed in case pre-7.3 DB: */
+ addObjectDependency(&attrdefs[j].dobj,
+ tbinfo->dobj.dumpId);
+ }
+ else
+ {
+ attrdefs[j].separate = false;
+
+ /*
+ * 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);
+ }
+
+ /*
+ * Get info about table CHECK constraints
+ */
+ if (tbinfo->ncheck > 0)
+ {
+ ConstraintInfo *constrs;
+ int numConstrs;
+
+ if (g_verbose)
+ write_msg(NULL, "finding check constraints for table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ resetPQExpBuffer(q);
+ 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).
+ */
+ 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);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ /* 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);
+ }
+ else if (fout->remoteVersion >= 70400)
+ {
+ 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);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ /* no pg_get_constraintdef, must use consrc */
+ appendPQExpBuffer(q, "SELECT tableoid, oid, conname, "
+ "'CHECK (' || consrc || ')' 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);
+ }
+ else if (fout->remoteVersion >= 70200)
+ {
+ /* 7.2 did not have OIDs in pg_relcheck */
+ appendPQExpBuffer(q, "SELECT tableoid, 0 AS oid, "
+ "rcname AS conname, "
+ "'CHECK (' || rcsrc || ')' AS consrc, "
+ "true AS conislocal, true AS convalidated "
+ "FROM pg_relcheck "
+ "WHERE rcrelid = '%u'::oid "
+ "ORDER BY rcname",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(q, "SELECT tableoid, oid, "
+ "rcname AS conname, "
+ "'CHECK (' || rcsrc || ')' AS consrc, "
+ "true AS conislocal, true AS convalidated "
+ "FROM pg_relcheck "
+ "WHERE rcrelid = '%u'::oid "
+ "ORDER BY rcname",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ /* no tableoid in 7.0 */
+ appendPQExpBuffer(q, "SELECT "
+ "(SELECT oid FROM pg_class WHERE relname = 'pg_relcheck') AS tableoid, "
+ "oid, rcname AS conname, "
+ "'CHECK (' || rcsrc || ')' AS consrc, "
+ "true AS conislocal, true AS convalidated "
+ "FROM pg_relcheck "
+ "WHERE rcrelid = '%u'::oid "
+ "ORDER BY rcname",
+ tbinfo->dobj.catId.oid);
+ }
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+
+ numConstrs = PQntuples(res);
+ if (numConstrs != tbinfo->ncheck)
+ {
+ write_msg(NULL, ngettext("expected %d check constraint on table \"%s\" but found %d\n",
+ "expected %d check constraints on table \"%s\" but found %d\n",
+ tbinfo->ncheck),
+ tbinfo->ncheck, tbinfo->dobj.name, numConstrs);
+ write_msg(NULL, "(The system catalogs might be corrupted.)\n");
+ exit_nicely(1);
+ }
+
+ constrs = (ConstraintInfo *) pg_malloc(numConstrs * sizeof(ConstraintInfo));
+ tbinfo->checkexprs = constrs;
+
+ for (j = 0; j < numConstrs; j++)
+ {
+ bool validated = PQgetvalue(res, j, 5)[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));
+ AssignDumpId(&constrs[j].dobj);
+ constrs[j].dobj.name = pg_strdup(PQgetvalue(res, j, 2));
+ 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].confrelid = InvalidOid;
+ constrs[j].conindex = 0;
+ constrs[j].condeferrable = false;
+ constrs[j].condeferred = false;
+ constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
+
+ /*
+ * An unvalidated constraint needs to be dumped separately, so
+ * that potentially-violating existing data is loaded before
+ * the constraint.
+ */
+ constrs[j].separate = !validated;
+
+ constrs[j].dobj.dump = tbinfo->dobj.dump;
+
+ /*
+ * Mark the constraint as needing to appear before the table
+ * --- this is so that any other dependencies of the
+ * constraint will be emitted before we try to create the
+ * table. If the constraint is to be dumped separately, it
+ * will be dumped after data is loaded anyway, so don't do it.
+ * (There's an automatic dependency in the opposite direction
+ * anyway, so don't need to add one manually here.)
+ */
+ if (!constrs[j].separate)
+ addObjectDependency(&tbinfo->dobj,
+ constrs[j].dobj.dumpId);
+
+ /*
+ * If the constraint is inherited, this will be detected later
+ * (in pre-8.4 databases). We also detect later if the
+ * constraint must be split out from the table definition.
+ */
+ }
+ PQclear(res);
+ }
+ }
+
+ destroyPQExpBuffer(q);
+}
+
+/*
+ * Test whether a column should be printed as part of table's CREATE TABLE.
+ * Column number is zero-based.
+ *
+ * Normally this is always true, but it's false for dropped columns, as well
+ * as those that were inherited without any local definition. (If we print
+ * such a column it will mistakenly get pg_attribute.attislocal set to true.)
+ * However, in binary_upgrade mode, we must print all such columns anyway and
+ * fix the attislocal/attisdropped state later, so as to keep control of the
+ * physical column order.
+ *
+ * This function exists because there are scattered nonobvious places that
+ * must be kept in sync with this decision.
+ */
+bool
+shouldPrintColumn(TableInfo *tbinfo, int colno)
+{
+ if (binary_upgrade)
+ return true;
+ return (tbinfo->attislocal[colno] && !tbinfo->attisdropped[colno]);
+}
+
+
+/*
+ * getTSParsers:
+ * read all text search parsers in the system catalogs and return them
+ * in the TSParserInfo* structure
+ *
+ * numTSParsers is set to the number of parsers read in
+ */
+TSParserInfo *
+getTSParsers(Archive *fout, int *numTSParsers)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ TSParserInfo *prsinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_prsname;
+ int i_prsnamespace;
+ int i_prsstart;
+ int i_prstoken;
+ int i_prsend;
+ int i_prsheadline;
+ int i_prslextype;
+
+ /* Before 8.3, there is no built-in text search support */
+ if (fout->remoteVersion < 80300)
+ {
+ *numTSParsers = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /*
+ * find all text search objects, including builtin ones; we filter out
+ * system-defined objects at dump-out time.
+ */
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, prsname, prsnamespace, "
+ "prsstart::oid, prstoken::oid, "
+ "prsend::oid, prsheadline::oid, prslextype::oid "
+ "FROM pg_ts_parser");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numTSParsers = ntups;
+
+ prsinfo = (TSParserInfo *) pg_malloc(ntups * sizeof(TSParserInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_prsname = PQfnumber(res, "prsname");
+ i_prsnamespace = PQfnumber(res, "prsnamespace");
+ i_prsstart = PQfnumber(res, "prsstart");
+ i_prstoken = PQfnumber(res, "prstoken");
+ i_prsend = PQfnumber(res, "prsend");
+ i_prsheadline = PQfnumber(res, "prsheadline");
+ i_prslextype = PQfnumber(res, "prslextype");
+
+ for (i = 0; i < ntups; i++)
+ {
+ prsinfo[i].dobj.objType = DO_TSPARSER;
+ prsinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ prsinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&prsinfo[i].dobj);
+ prsinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_prsname));
+ prsinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_prsnamespace)),
+ prsinfo[i].dobj.catId.oid);
+ prsinfo[i].prsstart = atooid(PQgetvalue(res, i, i_prsstart));
+ prsinfo[i].prstoken = atooid(PQgetvalue(res, i, i_prstoken));
+ prsinfo[i].prsend = atooid(PQgetvalue(res, i, i_prsend));
+ prsinfo[i].prsheadline = atooid(PQgetvalue(res, i, i_prsheadline));
+ prsinfo[i].prslextype = atooid(PQgetvalue(res, i, i_prslextype));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(prsinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return prsinfo;
+}
+
+/*
+ * getTSDictionaries:
+ * read all text search dictionaries in the system catalogs and return them
+ * in the TSDictInfo* structure
+ *
+ * numTSDicts is set to the number of dictionaries read in
+ */
+TSDictInfo *
+getTSDictionaries(Archive *fout, int *numTSDicts)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ TSDictInfo *dictinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_dictname;
+ int i_dictnamespace;
+ int i_rolname;
+ int i_dicttemplate;
+ int i_dictinitoption;
+
+ /* Before 8.3, there is no built-in text search support */
+ if (fout->remoteVersion < 80300)
+ {
+ *numTSDicts = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, dictname, "
+ "dictnamespace, (%s dictowner) AS rolname, "
+ "dicttemplate, dictinitoption "
+ "FROM pg_ts_dict",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numTSDicts = ntups;
+
+ dictinfo = (TSDictInfo *) pg_malloc(ntups * sizeof(TSDictInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_dictname = PQfnumber(res, "dictname");
+ i_dictnamespace = PQfnumber(res, "dictnamespace");
+ i_rolname = PQfnumber(res, "rolname");
+ i_dictinitoption = PQfnumber(res, "dictinitoption");
+ i_dicttemplate = PQfnumber(res, "dicttemplate");
+
+ for (i = 0; i < ntups; i++)
+ {
+ dictinfo[i].dobj.objType = DO_TSDICT;
+ dictinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ dictinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&dictinfo[i].dobj);
+ dictinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_dictname));
+ dictinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_dictnamespace)),
+ dictinfo[i].dobj.catId.oid);
+ dictinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ dictinfo[i].dicttemplate = atooid(PQgetvalue(res, i, i_dicttemplate));
+ if (PQgetisnull(res, i, i_dictinitoption))
+ dictinfo[i].dictinitoption = NULL;
+ else
+ dictinfo[i].dictinitoption = pg_strdup(PQgetvalue(res, i, i_dictinitoption));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(dictinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return dictinfo;
+}
+
+/*
+ * getTSTemplates:
+ * read all text search templates in the system catalogs and return them
+ * in the TSTemplateInfo* structure
+ *
+ * numTSTemplates is set to the number of templates read in
+ */
+TSTemplateInfo *
+getTSTemplates(Archive *fout, int *numTSTemplates)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ TSTemplateInfo *tmplinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_tmplname;
+ int i_tmplnamespace;
+ int i_tmplinit;
+ int i_tmpllexize;
+
+ /* Before 8.3, there is no built-in text search support */
+ if (fout->remoteVersion < 80300)
+ {
+ *numTSTemplates = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBufferStr(query, "SELECT tableoid, oid, tmplname, "
+ "tmplnamespace, tmplinit::oid, tmpllexize::oid "
+ "FROM pg_ts_template");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numTSTemplates = ntups;
+
+ tmplinfo = (TSTemplateInfo *) pg_malloc(ntups * sizeof(TSTemplateInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_tmplname = PQfnumber(res, "tmplname");
+ i_tmplnamespace = PQfnumber(res, "tmplnamespace");
+ i_tmplinit = PQfnumber(res, "tmplinit");
+ i_tmpllexize = PQfnumber(res, "tmpllexize");
+
+ for (i = 0; i < ntups; i++)
+ {
+ tmplinfo[i].dobj.objType = DO_TSTEMPLATE;
+ tmplinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ tmplinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&tmplinfo[i].dobj);
+ tmplinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_tmplname));
+ tmplinfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_tmplnamespace)),
+ tmplinfo[i].dobj.catId.oid);
+ tmplinfo[i].tmplinit = atooid(PQgetvalue(res, i, i_tmplinit));
+ tmplinfo[i].tmpllexize = atooid(PQgetvalue(res, i, i_tmpllexize));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(tmplinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return tmplinfo;
+}
+
+/*
+ * getTSConfigurations:
+ * read all text search configurations in the system catalogs and return
+ * them in the TSConfigInfo* structure
+ *
+ * numTSConfigs is set to the number of configurations read in
+ */
+TSConfigInfo *
+getTSConfigurations(Archive *fout, int *numTSConfigs)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ TSConfigInfo *cfginfo;
+ int i_tableoid;
+ int i_oid;
+ int i_cfgname;
+ int i_cfgnamespace;
+ int i_rolname;
+ int i_cfgparser;
+
+ /* Before 8.3, there is no built-in text search support */
+ if (fout->remoteVersion < 80300)
+ {
+ *numTSConfigs = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, cfgname, "
+ "cfgnamespace, (%s cfgowner) AS rolname, cfgparser "
+ "FROM pg_ts_config",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numTSConfigs = ntups;
+
+ cfginfo = (TSConfigInfo *) pg_malloc(ntups * sizeof(TSConfigInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_cfgname = PQfnumber(res, "cfgname");
+ i_cfgnamespace = PQfnumber(res, "cfgnamespace");
+ i_rolname = PQfnumber(res, "rolname");
+ i_cfgparser = PQfnumber(res, "cfgparser");
+
+ for (i = 0; i < ntups; i++)
+ {
+ cfginfo[i].dobj.objType = DO_TSCONFIG;
+ cfginfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ cfginfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&cfginfo[i].dobj);
+ cfginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_cfgname));
+ cfginfo[i].dobj.namespace =
+ findNamespace(fout,
+ atooid(PQgetvalue(res, i, i_cfgnamespace)),
+ cfginfo[i].dobj.catId.oid);
+ cfginfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ cfginfo[i].cfgparser = atooid(PQgetvalue(res, i, i_cfgparser));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(cfginfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return cfginfo;
+}
+
+/*
+ * getForeignDataWrappers:
+ * read all foreign-data wrappers in the system catalogs and return
+ * them in the FdwInfo* structure
+ *
+ * numForeignDataWrappers is set to the number of fdws read in
+ */
+FdwInfo *
+getForeignDataWrappers(Archive *fout, int *numForeignDataWrappers)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ FdwInfo *fdwinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_fdwname;
+ int i_rolname;
+ int i_fdwhandler;
+ int i_fdwvalidator;
+ int i_fdwacl;
+ int i_fdwoptions;
+
+ /* Before 8.4, there are no foreign-data wrappers */
+ if (fout->remoteVersion < 80400)
+ {
+ *numForeignDataWrappers = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 90100)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
+ "(%s fdwowner) AS rolname, "
+ "fdwhandler::pg_catalog.regproc, "
+ "fdwvalidator::pg_catalog.regproc, fdwacl, "
+ "array_to_string(ARRAY("
+ "SELECT quote_ident(option_name) || ' ' || "
+ "quote_literal(option_value) "
+ "FROM pg_options_to_table(fdwoptions) "
+ "ORDER BY option_name"
+ "), E',\n ') AS fdwoptions "
+ "FROM pg_foreign_data_wrapper",
+ username_subquery);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
+ "(%s fdwowner) AS rolname, "
+ "'-' AS fdwhandler, "
+ "fdwvalidator::pg_catalog.regproc, fdwacl, "
+ "array_to_string(ARRAY("
+ "SELECT quote_ident(option_name) || ' ' || "
+ "quote_literal(option_value) "
+ "FROM pg_options_to_table(fdwoptions) "
+ "ORDER BY option_name"
+ "), E',\n ') AS fdwoptions "
+ "FROM pg_foreign_data_wrapper",
+ username_subquery);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numForeignDataWrappers = ntups;
+
+ fdwinfo = (FdwInfo *) pg_malloc(ntups * sizeof(FdwInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_fdwname = PQfnumber(res, "fdwname");
+ i_rolname = PQfnumber(res, "rolname");
+ i_fdwhandler = PQfnumber(res, "fdwhandler");
+ i_fdwvalidator = PQfnumber(res, "fdwvalidator");
+ i_fdwacl = PQfnumber(res, "fdwacl");
+ i_fdwoptions = PQfnumber(res, "fdwoptions");
+
+ for (i = 0; i < ntups; i++)
+ {
+ fdwinfo[i].dobj.objType = DO_FDW;
+ fdwinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ fdwinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&fdwinfo[i].dobj);
+ fdwinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_fdwname));
+ fdwinfo[i].dobj.namespace = 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));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(fdwinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return fdwinfo;
+}
+
+/*
+ * getForeignServers:
+ * read all foreign servers in the system catalogs and return
+ * them in the ForeignServerInfo * structure
+ *
+ * numForeignServers is set to the number of servers read in
+ */
+ForeignServerInfo *
+getForeignServers(Archive *fout, int *numForeignServers)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ ForeignServerInfo *srvinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_srvname;
+ int i_rolname;
+ int i_srvfdw;
+ int i_srvtype;
+ int i_srvversion;
+ int i_srvacl;
+ int i_srvoptions;
+
+ /* Before 8.4, there are no foreign servers */
+ if (fout->remoteVersion < 80400)
+ {
+ *numForeignServers = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, "
+ "(%s srvowner) AS rolname, "
+ "srvfdw, srvtype, srvversion, srvacl,"
+ "array_to_string(ARRAY("
+ "SELECT quote_ident(option_name) || ' ' || "
+ "quote_literal(option_value) "
+ "FROM pg_options_to_table(srvoptions) "
+ "ORDER BY option_name"
+ "), E',\n ') AS srvoptions "
+ "FROM pg_foreign_server",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numForeignServers = ntups;
+
+ srvinfo = (ForeignServerInfo *) pg_malloc(ntups * sizeof(ForeignServerInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_srvname = PQfnumber(res, "srvname");
+ i_rolname = PQfnumber(res, "rolname");
+ i_srvfdw = PQfnumber(res, "srvfdw");
+ i_srvtype = PQfnumber(res, "srvtype");
+ i_srvversion = PQfnumber(res, "srvversion");
+ i_srvacl = PQfnumber(res, "srvacl");
+ i_srvoptions = PQfnumber(res, "srvoptions");
+
+ for (i = 0; i < ntups; i++)
+ {
+ srvinfo[i].dobj.objType = DO_FOREIGN_SERVER;
+ srvinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ srvinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&srvinfo[i].dobj);
+ srvinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_srvname));
+ srvinfo[i].dobj.namespace = 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));
+
+ /* Decide whether we want to dump it */
+ selectDumpableObject(&(srvinfo[i].dobj));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return srvinfo;
+}
+
+/*
+ * getDefaultACLs:
+ * read all default ACL information in the system catalogs and return
+ * them in the DefaultACLInfo structure
+ *
+ * numDefaultACLs is set to the number of ACLs read in
+ */
+DefaultACLInfo *
+getDefaultACLs(Archive *fout, int *numDefaultACLs)
+{
+ DefaultACLInfo *daclinfo;
+ PQExpBuffer query;
+ PGresult *res;
+ int i_oid;
+ int i_tableoid;
+ int i_defaclrole;
+ int i_defaclnamespace;
+ int i_defaclobjtype;
+ int i_defaclacl;
+ int i,
+ ntups;
+
+ if (fout->remoteVersion < 90000)
+ {
+ *numDefaultACLs = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT oid, tableoid, "
+ "(%s defaclrole) AS defaclrole, "
+ "defaclnamespace, "
+ "defaclobjtype, "
+ "defaclacl "
+ "FROM pg_default_acl",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ *numDefaultACLs = ntups;
+
+ daclinfo = (DefaultACLInfo *) pg_malloc(ntups * sizeof(DefaultACLInfo));
+
+ i_oid = PQfnumber(res, "oid");
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_defaclrole = PQfnumber(res, "defaclrole");
+ i_defaclnamespace = PQfnumber(res, "defaclnamespace");
+ i_defaclobjtype = PQfnumber(res, "defaclobjtype");
+ i_defaclacl = PQfnumber(res, "defaclacl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ Oid nspid = atooid(PQgetvalue(res, i, i_defaclnamespace));
+
+ daclinfo[i].dobj.objType = DO_DEFAULT_ACL;
+ daclinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ daclinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&daclinfo[i].dobj);
+ /* cheesy ... is it worth coming up with a better object name? */
+ daclinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_defaclobjtype));
+
+ if (nspid != InvalidOid)
+ daclinfo[i].dobj.namespace = findNamespace(fout, nspid,
+ daclinfo[i].dobj.catId.oid);
+ else
+ daclinfo[i].dobj.namespace = 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));
+
+ /* Decide whether we want to dump it */
+ selectDumpableDefaultACL(&(daclinfo[i]));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return daclinfo;
+}
+
+/*
+ * dumpComment --
+ *
+ * This routine is used to dump any comments associated with the
+ * object handed to this routine. The routine takes a constant character
+ * string for the target part of the comment-creation command, plus
+ * the namespace and owner of the object (for labeling the ArchiveEntry),
+ * plus catalog ID and subid which are the lookup key for pg_description,
+ * plus the dump ID for the object (for setting a dependency).
+ * If a matching pg_description entry is found, it is dumped.
+ *
+ * Note: although this routine takes a dumpId for dependency purposes,
+ * that purpose is just to mark the dependency in the emitted dump file
+ * for possible future use by pg_restore. We do NOT use it for determining
+ * ordering of the comment in the dump file, because this routine is called
+ * after dependency sorting occurs. This routine should be called just after
+ * calling ArchiveEntry() for the specified object.
+ */
+static void
+dumpComment(Archive *fout, const char *target,
+ const char *namespace, const char *owner,
+ CatalogId catalogId, int subid, DumpId dumpId)
+{
+ CommentItem *comments;
+ int ncomments;
+
+ /* Comments are schema not data ... except blob comments are data */
+ if (strncmp(target, "LARGE OBJECT ", 13) != 0)
+ {
+ if (dataOnly)
+ return;
+ }
+ else
+ {
+ if (schemaOnly)
+ return;
+ }
+
+ /* Search for comments associated with catalogId, using table */
+ ncomments = findComments(fout, catalogId.tableoid, catalogId.oid,
+ &comments);
+
+ /* Is there one matching the subid? */
+ while (ncomments > 0)
+ {
+ if (comments->objsubid == subid)
+ break;
+ comments++;
+ ncomments--;
+ }
+
+ /* If a comment exists, build COMMENT ON statement */
+ if (ncomments > 0)
+ {
+ PQExpBuffer query = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "COMMENT ON %s IS ", target);
+ appendStringLiteralAH(query, comments->descr, fout);
+ appendPQExpBufferStr(query, ";\n");
+
+ /*
+ * We mark comments as SECTION_NONE because they really belong in the
+ * same section as their parent, whether that is pre-data or
+ * post-data.
+ */
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ target, namespace, NULL, owner,
+ false, "COMMENT", SECTION_NONE,
+ query->data, "", NULL,
+ &(dumpId), 1,
+ NULL, NULL);
+
+ destroyPQExpBuffer(query);
+ }
+}
+
+/*
+ * dumpTableComment --
+ *
+ * As above, but dump comments for both the specified table (or view)
+ * and its columns.
+ */
+static void
+dumpTableComment(Archive *fout, TableInfo *tbinfo,
+ const char *reltypename)
+{
+ CommentItem *comments;
+ int ncomments;
+ PQExpBuffer query;
+ PQExpBuffer target;
+
+ /* Comments are SCHEMA not data */
+ if (dataOnly)
+ return;
+
+ /* Search for comments associated with relation, using table */
+ ncomments = findComments(fout,
+ tbinfo->dobj.catId.tableoid,
+ tbinfo->dobj.catId.oid,
+ &comments);
+
+ /* If comments exist, build COMMENT ON statements */
+ if (ncomments <= 0)
+ return;
+
+ query = createPQExpBuffer();
+ target = createPQExpBuffer();
+
+ while (ncomments > 0)
+ {
+ const char *descr = comments->descr;
+ int objsubid = comments->objsubid;
+
+ if (objsubid == 0)
+ {
+ resetPQExpBuffer(target);
+ appendPQExpBuffer(target, "%s %s", reltypename,
+ fmtId(tbinfo->dobj.name));
+
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query, "COMMENT ON %s IS ", target->data);
+ appendStringLiteralAH(query, descr, fout);
+ appendPQExpBufferStr(query, ";\n");
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ target->data,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL, tbinfo->rolname,
+ false, "COMMENT", SECTION_NONE,
+ query->data, "", NULL,
+ &(tbinfo->dobj.dumpId), 1,
+ NULL, NULL);
+ }
+ else if (objsubid > 0 && objsubid <= tbinfo->numatts)
+ {
+ resetPQExpBuffer(target);
+ appendPQExpBuffer(target, "COLUMN %s.",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBufferStr(target, fmtId(tbinfo->attnames[objsubid - 1]));
+
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query, "COMMENT ON %s IS ", target->data);
+ appendStringLiteralAH(query, descr, fout);
+ appendPQExpBufferStr(query, ";\n");
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ target->data,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL, tbinfo->rolname,
+ false, "COMMENT", SECTION_NONE,
+ query->data, "", NULL,
+ &(tbinfo->dobj.dumpId), 1,
+ NULL, NULL);
+ }
+
+ comments++;
+ ncomments--;
+ }
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(target);
+}
+
+/*
+ * findComments --
+ *
+ * Find the comment(s), if any, associated with the given object. All the
+ * objsubid values associated with the given classoid/objoid are found with
+ * one search.
+ */
+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);
+
+ /*
+ * Pre-7.2, pg_description does not contain classoid, so collectComments
+ * just stores a zero. If there's a collision on object OID, well, you
+ * get duplicate comments.
+ */
+ if (fout->remoteVersion < 70200)
+ classoid = 0;
+
+ /*
+ * Do binary search to find some item matching the object.
+ */
+ low = &comments[0];
+ high = &comments[ncomments - 1];
+ while (low <= high)
+ {
+ middle = low + (high - low) / 2;
+
+ if (classoid < middle->classoid)
+ high = middle - 1;
+ else if (classoid > middle->classoid)
+ low = middle + 1;
+ else if (objoid < middle->objoid)
+ high = middle - 1;
+ else if (objoid > middle->objoid)
+ low = middle + 1;
+ else
+ break; /* found a match */
+ }
+
+ if (low > high) /* no matches */
+ {
+ *items = NULL;
+ return 0;
+ }
+
+ /*
+ * Now determine how many items match the object. The search loop
+ * invariant still holds: only items between low and high inclusive could
+ * match.
+ */
+ nmatch = 1;
+ while (middle > low)
+ {
+ if (classoid != middle[-1].classoid ||
+ objoid != middle[-1].objoid)
+ break;
+ middle--;
+ nmatch++;
+ }
+
+ *items = middle;
+
+ middle += nmatch;
+ while (middle <= high)
+ {
+ if (classoid != middle->classoid ||
+ objoid != middle->objoid)
+ break;
+ middle++;
+ nmatch++;
+ }
+
+ return nmatch;
+}
+
+/*
+ * collectComments --
+ *
+ * Construct a table of all comments available for database objects.
+ * 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)
+{
+ PGresult *res;
+ PQExpBuffer query;
+ int i_description;
+ int i_classoid;
+ int i_objoid;
+ int i_objsubid;
+ int ntups;
+ int i;
+ CommentItem *comments;
+
+ /*
+ * Note we do NOT change source schema here; preserve the caller's
+ * setting, instead.
+ */
+
+ query = createPQExpBuffer();
+
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBufferStr(query, "SELECT description, classoid, objoid, objsubid "
+ "FROM pg_catalog.pg_description "
+ "ORDER BY classoid, objoid, objsubid");
+ }
+ else if (fout->remoteVersion >= 70200)
+ {
+ appendPQExpBufferStr(query, "SELECT description, classoid, objoid, objsubid "
+ "FROM pg_description "
+ "ORDER BY classoid, objoid, objsubid");
+ }
+ else
+ {
+ /* Note: this will fail to find attribute comments in pre-7.2... */
+ appendPQExpBufferStr(query, "SELECT description, 0 AS classoid, objoid, 0 AS objsubid "
+ "FROM pg_description "
+ "ORDER BY objoid");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ /* Construct lookup table containing OIDs in numeric form */
+
+ i_description = PQfnumber(res, "description");
+ i_classoid = PQfnumber(res, "classoid");
+ i_objoid = PQfnumber(res, "objoid");
+ i_objsubid = PQfnumber(res, "objsubid");
+
+ ntups = PQntuples(res);
+
+ comments = (CommentItem *) pg_malloc(ntups * sizeof(CommentItem));
+
+ 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));
+ }
+
+ /* Do NOT free the PGresult since we are keeping pointers into it */
+ destroyPQExpBuffer(query);
+
+ *items = comments;
+ return ntups;
+}
+
+/*
+ * dumpDumpableObject
+ *
+ * This routine and its subsidiaries are responsible for creating
+ * ArchiveEntries (TOC objects) for each object to be dumped.
+ */
+static void
+dumpDumpableObject(Archive *fout, DumpableObject *dobj)
+{
+ switch (dobj->objType)
+ {
+ case DO_NAMESPACE:
+ dumpNamespace(fout, (NamespaceInfo *) dobj);
+ break;
+ case DO_EXTENSION:
+ dumpExtension(fout, (ExtensionInfo *) dobj);
+ break;
+ case DO_TYPE:
+ dumpType(fout, (TypeInfo *) dobj);
+ break;
+ case DO_SHELL_TYPE:
+ dumpShellType(fout, (ShellTypeInfo *) dobj);
+ break;
+ case DO_FUNC:
+ dumpFunc(fout, (FuncInfo *) dobj);
+ break;
+ case DO_AGG:
+ dumpAgg(fout, (AggInfo *) dobj);
+ break;
+ case DO_OPERATOR:
+ dumpOpr(fout, (OprInfo *) dobj);
+ break;
+ case DO_OPCLASS:
+ dumpOpclass(fout, (OpclassInfo *) dobj);
+ break;
+ case DO_OPFAMILY:
+ dumpOpfamily(fout, (OpfamilyInfo *) dobj);
+ break;
+ case DO_COLLATION:
+ dumpCollation(fout, (CollInfo *) dobj);
+ break;
+ case DO_CONVERSION:
+ dumpConversion(fout, (ConvInfo *) dobj);
+ break;
+ case DO_TABLE:
+ dumpTable(fout, (TableInfo *) dobj);
+ break;
+ case DO_ATTRDEF:
+ dumpAttrDef(fout, (AttrDefInfo *) dobj);
+ break;
+ case DO_INDEX:
+ dumpIndex(fout, (IndxInfo *) dobj);
+ break;
+ case DO_REFRESH_MATVIEW:
+ refreshMatViewData(fout, (TableDataInfo *) dobj);
+ break;
+ case DO_RULE:
+ dumpRule(fout, (RuleInfo *) dobj);
+ break;
+ case DO_TRIGGER:
+ dumpTrigger(fout, (TriggerInfo *) dobj);
+ break;
+ case DO_EVENT_TRIGGER:
+ dumpEventTrigger(fout, (EventTriggerInfo *) dobj);
+ break;
+ case DO_CONSTRAINT:
+ dumpConstraint(fout, (ConstraintInfo *) dobj);
+ break;
+ case DO_FK_CONSTRAINT:
+ dumpConstraint(fout, (ConstraintInfo *) dobj);
+ break;
+ case DO_PROCLANG:
+ dumpProcLang(fout, (ProcLangInfo *) dobj);
+ break;
+ case DO_CAST:
+ dumpCast(fout, (CastInfo *) dobj);
+ break;
+ case DO_TABLE_DATA:
+ if (((TableDataInfo *) dobj)->tdtable->relkind == RELKIND_SEQUENCE)
+ dumpSequenceData(fout, (TableDataInfo *) dobj);
+ else
+ dumpTableData(fout, (TableDataInfo *) dobj);
+ break;
+ case DO_DUMMY_TYPE:
+ /* table rowtypes and array types are never dumped separately */
+ break;
+ case DO_TSPARSER:
+ dumpTSParser(fout, (TSParserInfo *) dobj);
+ break;
+ case DO_TSDICT:
+ dumpTSDictionary(fout, (TSDictInfo *) dobj);
+ break;
+ case DO_TSTEMPLATE:
+ dumpTSTemplate(fout, (TSTemplateInfo *) dobj);
+ break;
+ case DO_TSCONFIG:
+ dumpTSConfig(fout, (TSConfigInfo *) dobj);
+ break;
+ case DO_FDW:
+ dumpForeignDataWrapper(fout, (FdwInfo *) dobj);
+ break;
+ case DO_FOREIGN_SERVER:
+ dumpForeignServer(fout, (ForeignServerInfo *) dobj);
+ break;
+ case DO_DEFAULT_ACL:
+ dumpDefaultACL(fout, (DefaultACLInfo *) dobj);
+ break;
+ case DO_BLOB:
+ dumpBlob(fout, (BlobInfo *) dobj);
+ break;
+ case DO_BLOB_DATA:
+ ArchiveEntry(fout, dobj->catId, dobj->dumpId,
+ dobj->name, NULL, NULL, "",
+ false, "BLOBS", SECTION_DATA,
+ "", "", NULL,
+ NULL, 0,
+ dumpBlobs, NULL);
+ break;
+ case DO_PRE_DATA_BOUNDARY:
+ case DO_POST_DATA_BOUNDARY:
+ /* never dumped, nothing to do */
+ break;
+ }
+}
+
+/*
+ * dumpNamespace
+ * writes out to fout the queries to recreate a user-defined namespace
+ */
+static void
+dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ char *qnspname;
+
+ /* Skip if not to be dumped */
+ if (!nspinfo->dobj.dump || dataOnly)
+ return;
+
+ /* don't dump dummy namespace from pre-7.3 source */
+ if (strlen(nspinfo->dobj.name) == 0)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ qnspname = pg_strdup(fmtId(nspinfo->dobj.name));
+
+ appendPQExpBuffer(delq, "DROP SCHEMA %s;\n", qnspname);
+
+ appendPQExpBuffer(q, "CREATE SCHEMA %s;\n", qnspname);
+
+ appendPQExpBuffer(labelq, "SCHEMA %s", qnspname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &nspinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, nspinfo->dobj.catId, nspinfo->dobj.dumpId,
+ nspinfo->dobj.name,
+ NULL, NULL,
+ nspinfo->rolname,
+ false, "SCHEMA", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Schema Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ NULL, nspinfo->rolname,
+ nspinfo->dobj.catId, 0, nspinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ NULL, nspinfo->rolname,
+ nspinfo->dobj.catId, 0, nspinfo->dobj.dumpId);
+
+ dumpACL(fout, nspinfo->dobj.catId, nspinfo->dobj.dumpId, "SCHEMA",
+ qnspname, NULL, nspinfo->dobj.name, NULL,
+ nspinfo->rolname, nspinfo->nspacl);
+
+ free(qnspname);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpExtension
+ * writes out to fout the queries to recreate an extension
+ */
+static void
+dumpExtension(Archive *fout, ExtensionInfo *extinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ char *qextname;
+
+ /* Skip if not to be dumped */
+ if (!extinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ qextname = pg_strdup(fmtId(extinfo->dobj.name));
+
+ appendPQExpBuffer(delq, "DROP EXTENSION %s;\n", qextname);
+
+ if (!binary_upgrade)
+ {
+ /*
+ * In a regular dump, we use IF NOT EXISTS so that there isn't a
+ * problem if the extension already exists in the target database;
+ * this is essential for installed-by-default extensions such as
+ * plpgsql.
+ *
+ * In binary-upgrade mode, that doesn't work well, so instead we skip
+ * built-in extensions based on their OIDs; see
+ * selectDumpableExtension.
+ */
+ appendPQExpBuffer(q, "CREATE EXTENSION IF NOT EXISTS %s WITH SCHEMA %s;\n",
+ qextname, fmtId(extinfo->namespace));
+ }
+ else
+ {
+ int i;
+ int n;
+
+ appendPQExpBufferStr(q, "-- For binary upgrade, create an empty extension and insert objects into it\n");
+
+ /*
+ * We unconditionally create the extension, so we must drop it if it
+ * exists. This could happen if the user deleted 'plpgsql' and then
+ * readded it, causing its oid to be greater than FirstNormalObjectId.
+ * The FirstNormalObjectId test was kept to avoid repeatedly dropping
+ * and recreating extensions like 'plpgsql'.
+ */
+ appendPQExpBuffer(q, "DROP EXTENSION IF EXISTS %s;\n", qextname);
+
+ appendPQExpBufferStr(q,
+ "SELECT binary_upgrade.create_empty_extension(");
+ appendStringLiteralAH(q, extinfo->dobj.name, fout);
+ appendPQExpBufferStr(q, ", ");
+ appendStringLiteralAH(q, extinfo->namespace, fout);
+ appendPQExpBufferStr(q, ", ");
+ appendPQExpBuffer(q, "%s, ", extinfo->relocatable ? "true" : "false");
+ appendStringLiteralAH(q, extinfo->extversion, fout);
+ appendPQExpBufferStr(q, ", ");
+
+ /*
+ * Note that we're pushing extconfig (an OID array) back into
+ * pg_extension exactly as-is. This is OK because pg_class OIDs are
+ * preserved in binary upgrade.
+ */
+ if (strlen(extinfo->extconfig) > 2)
+ appendStringLiteralAH(q, extinfo->extconfig, fout);
+ else
+ appendPQExpBufferStr(q, "NULL");
+ appendPQExpBufferStr(q, ", ");
+ if (strlen(extinfo->extcondition) > 2)
+ appendStringLiteralAH(q, extinfo->extcondition, fout);
+ else
+ appendPQExpBufferStr(q, "NULL");
+ appendPQExpBufferStr(q, ", ");
+ appendPQExpBufferStr(q, "ARRAY[");
+ n = 0;
+ for (i = 0; i < extinfo->dobj.nDeps; i++)
+ {
+ DumpableObject *extobj;
+
+ extobj = findObjectByDumpId(extinfo->dobj.dependencies[i]);
+ if (extobj && extobj->objType == DO_EXTENSION)
+ {
+ if (n++ > 0)
+ appendPQExpBufferChar(q, ',');
+ appendStringLiteralAH(q, extobj->name, fout);
+ }
+ }
+ appendPQExpBufferStr(q, "]::pg_catalog.text[]");
+ appendPQExpBufferStr(q, ");\n");
+ }
+
+ appendPQExpBuffer(labelq, "EXTENSION %s", qextname);
+
+ ArchiveEntry(fout, extinfo->dobj.catId, extinfo->dobj.dumpId,
+ extinfo->dobj.name,
+ NULL, NULL,
+ "",
+ false, "EXTENSION", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Extension Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ extinfo->dobj.catId, 0, extinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ NULL, "",
+ extinfo->dobj.catId, 0, extinfo->dobj.dumpId);
+
+ free(qextname);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpType
+ * writes out to fout the queries to recreate a user-defined type
+ */
+static void
+dumpType(Archive *fout, TypeInfo *tyinfo)
+{
+ /* Skip if not to be dumped */
+ if (!tyinfo->dobj.dump || dataOnly)
+ return;
+
+ /* Dump out in proper style */
+ if (tyinfo->typtype == TYPTYPE_BASE)
+ dumpBaseType(fout, tyinfo);
+ else if (tyinfo->typtype == TYPTYPE_DOMAIN)
+ dumpDomain(fout, tyinfo);
+ else if (tyinfo->typtype == TYPTYPE_COMPOSITE)
+ dumpCompositeType(fout, tyinfo);
+ else if (tyinfo->typtype == TYPTYPE_ENUM)
+ dumpEnumType(fout, tyinfo);
+ else if (tyinfo->typtype == TYPTYPE_RANGE)
+ dumpRangeType(fout, tyinfo);
+ else if (tyinfo->typtype == TYPTYPE_PSEUDO && !tyinfo->isDefined)
+ dumpUndefinedType(fout, tyinfo);
+ else
+ write_msg(NULL, "WARNING: typtype of data type \"%s\" appears to be invalid\n",
+ tyinfo->dobj.name);
+}
+
+/*
+ * dumpEnumType
+ * writes out to fout the queries to recreate a user-defined enum type
+ */
+static void
+dumpEnumType(Archive *fout, TypeInfo *tyinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ int num,
+ i;
+ Oid enum_oid;
+ char *qtypname;
+ char *label;
+
+ /* Set proper schema search path */
+ selectSourceSchema(fout, "pg_catalog");
+
+ 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);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ num = PQntuples(res);
+
+ qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog.
+ * CASCADE shouldn't be required here as for normal types since the I/O
+ * functions are generic and do not get dropped.
+ */
+ appendPQExpBuffer(delq, "DROP TYPE %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_type_oid(fout, q,
+ tyinfo->dobj.catId.oid);
+
+ appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
+ qtypname);
+
+ if (!binary_upgrade)
+ {
+ /* Labels with server-assigned oids */
+ for (i = 0; i < num; i++)
+ {
+ label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
+ if (i > 0)
+ appendPQExpBufferChar(q, ',');
+ appendPQExpBufferStr(q, "\n ");
+ appendStringLiteralAH(q, label, fout);
+ }
+ }
+
+ appendPQExpBufferStr(q, "\n);\n");
+
+ if (binary_upgrade)
+ {
+ /* Labels with dump-assigned (preserved) oids */
+ for (i = 0; i < num; i++)
+ {
+ enum_oid = atooid(PQgetvalue(res, i, PQfnumber(res, "oid")));
+ label = PQgetvalue(res, i, PQfnumber(res, "enumlabel"));
+
+ if (i == 0)
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
+ appendPQExpBuffer(q,
+ "SELECT binary_upgrade.set_next_pg_enum_oid('%u'::pg_catalog.oid);\n",
+ enum_oid);
+ appendPQExpBuffer(q, "ALTER TYPE %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(q, "%s ADD VALUE ",
+ qtypname);
+ appendStringLiteralAH(q, label, fout);
+ appendPQExpBufferStr(q, ";\n\n");
+ }
+ }
+
+ appendPQExpBuffer(labelq, "TYPE %s", qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
+ tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "TYPE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Type Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+
+ dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
+ qtypname, NULL, tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ tyinfo->rolname, tyinfo->typacl);
+
+ PQclear(res);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpRangeType
+ * writes out to fout the queries to recreate a user-defined range type
+ */
+static void
+dumpRangeType(Archive *fout, TypeInfo *tyinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ Oid collationOid;
+ char *qtypname;
+ char *procname;
+
+ /*
+ * select appropriate schema to ensure names in CREATE are properly
+ * qualified
+ */
+ selectSourceSchema(fout, tyinfo->dobj.namespace->dobj.name);
+
+ appendPQExpBuffer(query,
+ "SELECT 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'",
+ tyinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog.
+ * CASCADE shouldn't be required here as for normal types since the I/O
+ * functions are generic and do not get dropped.
+ */
+ appendPQExpBuffer(delq, "DROP TYPE %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_type_oid(fout,
+ q, tyinfo->dobj.catId.oid);
+
+ appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
+ qtypname);
+
+ appendPQExpBuffer(q, "\n subtype = %s",
+ PQgetvalue(res, 0, PQfnumber(res, "rngsubtype")));
+
+ /* print subtype_opclass only if not default for subtype */
+ if (PQgetvalue(res, 0, PQfnumber(res, "opcdefault"))[0] != 't')
+ {
+ char *opcname = PQgetvalue(res, 0, PQfnumber(res, "opcname"));
+ char *nspname = PQgetvalue(res, 0, PQfnumber(res, "opcnsp"));
+
+ /* always schema-qualify, don't try to be smart */
+ appendPQExpBuffer(q, ",\n subtype_opclass = %s.",
+ fmtId(nspname));
+ appendPQExpBufferStr(q, fmtId(opcname));
+ }
+
+ collationOid = atooid(PQgetvalue(res, 0, PQfnumber(res, "collation")));
+ if (OidIsValid(collationOid))
+ {
+ CollInfo *coll = findCollationByOid(collationOid);
+
+ if (coll)
+ {
+ /* always schema-qualify, don't try to be smart */
+ appendPQExpBuffer(q, ",\n collation = %s.",
+ fmtId(coll->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(coll->dobj.name));
+ }
+ }
+
+ procname = PQgetvalue(res, 0, PQfnumber(res, "rngcanonical"));
+ if (strcmp(procname, "-") != 0)
+ appendPQExpBuffer(q, ",\n canonical = %s", procname);
+
+ procname = PQgetvalue(res, 0, PQfnumber(res, "rngsubdiff"));
+ if (strcmp(procname, "-") != 0)
+ appendPQExpBuffer(q, ",\n subtype_diff = %s", procname);
+
+ appendPQExpBufferStr(q, "\n);\n");
+
+ appendPQExpBuffer(labelq, "TYPE %s", qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
+ tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "TYPE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Type Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+
+ dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
+ qtypname, NULL, tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ tyinfo->rolname, tyinfo->typacl);
+
+ PQclear(res);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpUndefinedType
+ * writes out to fout the queries to recreate a !typisdefined type
+ *
+ * This is a shell type, but we use different terminology to distinguish
+ * this case from where we have to emit a shell type definition to break
+ * circular dependencies. An undefined type shouldn't ever have anything
+ * depending on it.
+ */
+static void
+dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ char *qtypname;
+
+ qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog.
+ */
+ appendPQExpBuffer(delq, "DROP TYPE %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_type_oid(fout,
+ q, tyinfo->dobj.catId.oid);
+
+ appendPQExpBuffer(q, "CREATE TYPE %s;\n",
+ qtypname);
+
+ appendPQExpBuffer(labelq, "TYPE %s", qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
+ tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "TYPE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Type Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+
+ dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
+ qtypname, NULL, tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ tyinfo->rolname, tyinfo->typacl);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpBaseType
+ * writes out to fout the queries to recreate a user-defined base type
+ */
+static void
+dumpBaseType(Archive *fout, TypeInfo *tyinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ char *qtypname;
+ char *typlen;
+ char *typinput;
+ char *typoutput;
+ char *typreceive;
+ char *typsend;
+ char *typmodin;
+ char *typmodout;
+ char *typanalyze;
+ Oid typreceiveoid;
+ Oid typsendoid;
+ Oid typmodinoid;
+ Oid typmodoutoid;
+ Oid typanalyzeoid;
+ char *typcategory;
+ char *typispreferred;
+ char *typdelim;
+ char *typbyval;
+ char *typalign;
+ char *typstorage;
+ char *typcollatable;
+ char *typdefault;
+ bool typdefault_is_literal = false;
+
+ /* Set proper schema search path so regproc references list correctly */
+ selectSourceSchema(fout, tyinfo->dobj.namespace->dobj.name);
+
+ /* Fetch type-specific details */
+ if (fout->remoteVersion >= 90100)
+ {
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, typreceive, typsend, "
+ "typmodin, typmodout, typanalyze, "
+ "typreceive::pg_catalog.oid AS typreceiveoid, "
+ "typsend::pg_catalog.oid AS typsendoid, "
+ "typmodin::pg_catalog.oid AS typmodinoid, "
+ "typmodout::pg_catalog.oid AS typmodoutoid, "
+ "typanalyze::pg_catalog.oid AS typanalyzeoid, "
+ "typcategory, typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "(typcollation <> 0) AS typcollatable, "
+ "pg_catalog.pg_get_expr(typdefaultbin, 0) AS typdefaultbin, typdefault "
+ "FROM pg_catalog.pg_type "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, typreceive, typsend, "
+ "typmodin, typmodout, typanalyze, "
+ "typreceive::pg_catalog.oid AS typreceiveoid, "
+ "typsend::pg_catalog.oid AS typsendoid, "
+ "typmodin::pg_catalog.oid AS typmodinoid, "
+ "typmodout::pg_catalog.oid AS typmodoutoid, "
+ "typanalyze::pg_catalog.oid AS typanalyzeoid, "
+ "typcategory, typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "pg_catalog.pg_get_expr(typdefaultbin, 0) AS typdefaultbin, typdefault "
+ "FROM pg_catalog.pg_type "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80300)
+ {
+ /* Before 8.4, pg_get_expr does not allow 0 for its second arg */
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, typreceive, typsend, "
+ "typmodin, typmodout, typanalyze, "
+ "typreceive::pg_catalog.oid AS typreceiveoid, "
+ "typsend::pg_catalog.oid AS typsendoid, "
+ "typmodin::pg_catalog.oid AS typmodinoid, "
+ "typmodout::pg_catalog.oid AS typmodoutoid, "
+ "typanalyze::pg_catalog.oid AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, typdefault "
+ "FROM pg_catalog.pg_type "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80000)
+ {
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, typreceive, typsend, "
+ "'-' AS typmodin, '-' AS typmodout, "
+ "typanalyze, "
+ "typreceive::pg_catalog.oid AS typreceiveoid, "
+ "typsend::pg_catalog.oid AS typsendoid, "
+ "0 AS typmodinoid, 0 AS typmodoutoid, "
+ "typanalyze::pg_catalog.oid AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, typdefault "
+ "FROM pg_catalog.pg_type "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70400)
+ {
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, typreceive, typsend, "
+ "'-' AS typmodin, '-' AS typmodout, "
+ "'-' AS typanalyze, "
+ "typreceive::pg_catalog.oid AS typreceiveoid, "
+ "typsend::pg_catalog.oid AS typsendoid, "
+ "0 AS typmodinoid, 0 AS typmodoutoid, "
+ "0 AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, typdefault "
+ "FROM pg_catalog.pg_type "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, "
+ "'-' AS typreceive, '-' AS typsend, "
+ "'-' AS typmodin, '-' AS typmodout, "
+ "'-' AS typanalyze, "
+ "0 AS typreceiveoid, 0 AS typsendoid, "
+ "0 AS typmodinoid, 0 AS typmodoutoid, "
+ "0 AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, typdefault "
+ "FROM pg_catalog.pg_type "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70200)
+ {
+ /*
+ * Note: although pre-7.3 catalogs contain typreceive and typsend,
+ * ignore them because they are not right.
+ */
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, "
+ "'-' AS typreceive, '-' AS typsend, "
+ "'-' AS typmodin, '-' AS typmodout, "
+ "'-' AS typanalyze, "
+ "0 AS typreceiveoid, 0 AS typsendoid, "
+ "0 AS typmodinoid, 0 AS typmodoutoid, "
+ "0 AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "NULL AS typdefaultbin, typdefault "
+ "FROM pg_type "
+ "WHERE oid = '%u'::oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ /*
+ * Ignore pre-7.2 typdefault; the field exists but has an unusable
+ * representation.
+ */
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, "
+ "'-' AS typreceive, '-' AS typsend, "
+ "'-' AS typmodin, '-' AS typmodout, "
+ "'-' AS typanalyze, "
+ "0 AS typreceiveoid, 0 AS typsendoid, "
+ "0 AS typmodinoid, 0 AS typmodoutoid, "
+ "0 AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, typstorage, "
+ "false AS typcollatable, "
+ "NULL AS typdefaultbin, NULL AS typdefault "
+ "FROM pg_type "
+ "WHERE oid = '%u'::oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT typlen, "
+ "typinput, typoutput, "
+ "'-' AS typreceive, '-' AS typsend, "
+ "'-' AS typmodin, '-' AS typmodout, "
+ "'-' AS typanalyze, "
+ "0 AS typreceiveoid, 0 AS typsendoid, "
+ "0 AS typmodinoid, 0 AS typmodoutoid, "
+ "0 AS typanalyzeoid, "
+ "'U' AS typcategory, false AS typispreferred, "
+ "typdelim, typbyval, typalign, "
+ "'p'::char AS typstorage, "
+ "false AS typcollatable, "
+ "NULL AS typdefaultbin, NULL AS typdefault "
+ "FROM pg_type "
+ "WHERE oid = '%u'::oid",
+ tyinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ typlen = PQgetvalue(res, 0, PQfnumber(res, "typlen"));
+ typinput = PQgetvalue(res, 0, PQfnumber(res, "typinput"));
+ typoutput = PQgetvalue(res, 0, PQfnumber(res, "typoutput"));
+ typreceive = PQgetvalue(res, 0, PQfnumber(res, "typreceive"));
+ typsend = PQgetvalue(res, 0, PQfnumber(res, "typsend"));
+ typmodin = PQgetvalue(res, 0, PQfnumber(res, "typmodin"));
+ typmodout = PQgetvalue(res, 0, PQfnumber(res, "typmodout"));
+ typanalyze = PQgetvalue(res, 0, PQfnumber(res, "typanalyze"));
+ typreceiveoid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typreceiveoid")));
+ typsendoid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typsendoid")));
+ typmodinoid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typmodinoid")));
+ typmodoutoid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typmodoutoid")));
+ typanalyzeoid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typanalyzeoid")));
+ typcategory = PQgetvalue(res, 0, PQfnumber(res, "typcategory"));
+ typispreferred = PQgetvalue(res, 0, PQfnumber(res, "typispreferred"));
+ typdelim = PQgetvalue(res, 0, PQfnumber(res, "typdelim"));
+ typbyval = PQgetvalue(res, 0, PQfnumber(res, "typbyval"));
+ typalign = PQgetvalue(res, 0, PQfnumber(res, "typalign"));
+ typstorage = PQgetvalue(res, 0, PQfnumber(res, "typstorage"));
+ typcollatable = PQgetvalue(res, 0, PQfnumber(res, "typcollatable"));
+ if (!PQgetisnull(res, 0, PQfnumber(res, "typdefaultbin")))
+ typdefault = PQgetvalue(res, 0, PQfnumber(res, "typdefaultbin"));
+ else if (!PQgetisnull(res, 0, PQfnumber(res, "typdefault")))
+ {
+ typdefault = PQgetvalue(res, 0, PQfnumber(res, "typdefault"));
+ typdefault_is_literal = true; /* it needs quotes */
+ }
+ else
+ typdefault = NULL;
+
+ qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog.
+ * The reason we include CASCADE is that the circular dependency between
+ * the type and its I/O functions makes it impossible to drop the type any
+ * other way.
+ */
+ appendPQExpBuffer(delq, "DROP TYPE %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s CASCADE;\n",
+ qtypname);
+
+ /* We might already have a shell type, but setting pg_type_oid is harmless */
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_type_oid(fout, q,
+ tyinfo->dobj.catId.oid);
+
+ appendPQExpBuffer(q,
+ "CREATE TYPE %s (\n"
+ " INTERNALLENGTH = %s",
+ qtypname,
+ (strcmp(typlen, "-1") == 0) ? "variable" : typlen);
+
+ if (fout->remoteVersion >= 70300)
+ {
+ /* regproc result is correctly quoted as of 7.3 */
+ appendPQExpBuffer(q, ",\n INPUT = %s", typinput);
+ appendPQExpBuffer(q, ",\n OUTPUT = %s", typoutput);
+ if (OidIsValid(typreceiveoid))
+ appendPQExpBuffer(q, ",\n RECEIVE = %s", typreceive);
+ if (OidIsValid(typsendoid))
+ appendPQExpBuffer(q, ",\n SEND = %s", typsend);
+ if (OidIsValid(typmodinoid))
+ appendPQExpBuffer(q, ",\n TYPMOD_IN = %s", typmodin);
+ if (OidIsValid(typmodoutoid))
+ appendPQExpBuffer(q, ",\n TYPMOD_OUT = %s", typmodout);
+ if (OidIsValid(typanalyzeoid))
+ appendPQExpBuffer(q, ",\n ANALYZE = %s", typanalyze);
+ }
+ else
+ {
+ /* regproc delivers an unquoted name before 7.3 */
+ /* cannot combine these because fmtId uses static result area */
+ appendPQExpBuffer(q, ",\n INPUT = %s", fmtId(typinput));
+ appendPQExpBuffer(q, ",\n OUTPUT = %s", fmtId(typoutput));
+ /* receive/send/typmodin/typmodout/analyze need not be printed */
+ }
+
+ if (strcmp(typcollatable, "t") == 0)
+ appendPQExpBufferStr(q, ",\n COLLATABLE = true");
+
+ if (typdefault != NULL)
+ {
+ appendPQExpBufferStr(q, ",\n DEFAULT = ");
+ if (typdefault_is_literal)
+ appendStringLiteralAH(q, typdefault, fout);
+ else
+ appendPQExpBufferStr(q, typdefault);
+ }
+
+ if (OidIsValid(tyinfo->typelem))
+ {
+ char *elemType;
+
+ /* reselect schema in case changed by function dump */
+ selectSourceSchema(fout, tyinfo->dobj.namespace->dobj.name);
+ elemType = getFormattedTypeName(fout, tyinfo->typelem, zeroAsOpaque);
+ appendPQExpBuffer(q, ",\n ELEMENT = %s", elemType);
+ free(elemType);
+ }
+
+ if (strcmp(typcategory, "U") != 0)
+ {
+ appendPQExpBufferStr(q, ",\n CATEGORY = ");
+ appendStringLiteralAH(q, typcategory, fout);
+ }
+
+ if (strcmp(typispreferred, "t") == 0)
+ appendPQExpBufferStr(q, ",\n PREFERRED = true");
+
+ if (typdelim && strcmp(typdelim, ",") != 0)
+ {
+ appendPQExpBufferStr(q, ",\n DELIMITER = ");
+ appendStringLiteralAH(q, typdelim, fout);
+ }
+
+ if (strcmp(typalign, "c") == 0)
+ appendPQExpBufferStr(q, ",\n ALIGNMENT = char");
+ else if (strcmp(typalign, "s") == 0)
+ appendPQExpBufferStr(q, ",\n ALIGNMENT = int2");
+ else if (strcmp(typalign, "i") == 0)
+ appendPQExpBufferStr(q, ",\n ALIGNMENT = int4");
+ else if (strcmp(typalign, "d") == 0)
+ appendPQExpBufferStr(q, ",\n ALIGNMENT = double");
+
+ if (strcmp(typstorage, "p") == 0)
+ appendPQExpBufferStr(q, ",\n STORAGE = plain");
+ else if (strcmp(typstorage, "e") == 0)
+ appendPQExpBufferStr(q, ",\n STORAGE = external");
+ else if (strcmp(typstorage, "x") == 0)
+ appendPQExpBufferStr(q, ",\n STORAGE = extended");
+ else if (strcmp(typstorage, "m") == 0)
+ appendPQExpBufferStr(q, ",\n STORAGE = main");
+
+ if (strcmp(typbyval, "t") == 0)
+ appendPQExpBufferStr(q, ",\n PASSEDBYVALUE");
+
+ appendPQExpBufferStr(q, "\n);\n");
+
+ appendPQExpBuffer(labelq, "TYPE %s", qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
+ tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "TYPE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Type Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+
+ dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
+ qtypname, NULL, tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ tyinfo->rolname, tyinfo->typacl);
+
+ PQclear(res);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpDomain
+ * writes out to fout the queries to recreate a user-defined domain
+ */
+static void
+dumpDomain(Archive *fout, TypeInfo *tyinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ int i;
+ char *qtypname;
+ char *typnotnull;
+ char *typdefn;
+ char *typdefault;
+ Oid typcollation;
+ bool typdefault_is_literal = false;
+
+ /* Set proper schema search path so type references list correctly */
+ selectSourceSchema(fout, tyinfo->dobj.namespace->dobj.name);
+
+ /* Fetch domain specific details */
+ if (fout->remoteVersion >= 90100)
+ {
+ /* typcollation is new in 9.1 */
+ appendPQExpBuffer(query, "SELECT t.typnotnull, "
+ "pg_catalog.format_type(t.typbasetype, t.typtypmod) AS typdefn, "
+ "pg_catalog.pg_get_expr(t.typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, "
+ "t.typdefault, "
+ "CASE WHEN t.typcollation <> u.typcollation "
+ "THEN t.typcollation ELSE 0 END AS typcollation "
+ "FROM pg_catalog.pg_type t "
+ "LEFT JOIN pg_catalog.pg_type u ON (t.typbasetype = u.oid) "
+ "WHERE t.oid = '%u'::pg_catalog.oid",
+ tyinfo->dobj.catId.oid);
+ }
+ else
+ {
+ /* We assume here that remoteVersion must be at least 70300 */
+ 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);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ typnotnull = PQgetvalue(res, 0, PQfnumber(res, "typnotnull"));
+ typdefn = PQgetvalue(res, 0, PQfnumber(res, "typdefn"));
+ if (!PQgetisnull(res, 0, PQfnumber(res, "typdefaultbin")))
+ typdefault = PQgetvalue(res, 0, PQfnumber(res, "typdefaultbin"));
+ else if (!PQgetisnull(res, 0, PQfnumber(res, "typdefault")))
+ {
+ typdefault = PQgetvalue(res, 0, PQfnumber(res, "typdefault"));
+ typdefault_is_literal = true; /* it needs quotes */
+ }
+ else
+ typdefault = NULL;
+ typcollation = atooid(PQgetvalue(res, 0, PQfnumber(res, "typcollation")));
+
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_type_oid(fout, q,
+ tyinfo->dobj.catId.oid);
+
+ qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
+
+ appendPQExpBuffer(q,
+ "CREATE DOMAIN %s AS %s",
+ qtypname,
+ typdefn);
+
+ /* Print collation only if different from base type's collation */
+ if (OidIsValid(typcollation))
+ {
+ CollInfo *coll;
+
+ coll = findCollationByOid(typcollation);
+ if (coll)
+ {
+ /* always schema-qualify, don't try to be smart */
+ appendPQExpBuffer(q, " COLLATE %s.",
+ fmtId(coll->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(coll->dobj.name));
+ }
+ }
+
+ if (typnotnull[0] == 't')
+ appendPQExpBufferStr(q, " NOT NULL");
+
+ if (typdefault != NULL)
+ {
+ appendPQExpBufferStr(q, " DEFAULT ");
+ if (typdefault_is_literal)
+ appendStringLiteralAH(q, typdefault, fout);
+ else
+ appendPQExpBufferStr(q, typdefault);
+ }
+
+ PQclear(res);
+
+ /*
+ * Add any CHECK constraints for the domain
+ */
+ for (i = 0; i < tyinfo->nDomChecks; i++)
+ {
+ ConstraintInfo *domcheck = &(tyinfo->domChecks[i]);
+
+ if (!domcheck->separate)
+ appendPQExpBuffer(q, "\n\tCONSTRAINT %s %s",
+ fmtId(domcheck->dobj.name), domcheck->condef);
+ }
+
+ appendPQExpBufferStr(q, ";\n");
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP DOMAIN %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ qtypname);
+
+ appendPQExpBuffer(labelq, "DOMAIN %s", qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
+ tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "DOMAIN", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Domain Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+
+ dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
+ qtypname, NULL, tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ tyinfo->rolname, tyinfo->typacl);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpCompositeType
+ * writes out to fout the queries to recreate a user-defined stand-alone
+ * composite type
+ */
+static void
+dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer dropped = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ char *qtypname;
+ int ntups;
+ int i_attname;
+ int i_atttypdefn;
+ int i_attlen;
+ int i_attalign;
+ int i_attisdropped;
+ int i_attcollation;
+ int i;
+ int actual_atts;
+
+ /* Set proper schema search path so type references list correctly */
+ selectSourceSchema(fout, tyinfo->dobj.namespace->dobj.name);
+
+ /* 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
+ {
+ /*
+ * We assume here that remoteVersion must be at least 70300. 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);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_attname = PQfnumber(res, "attname");
+ i_atttypdefn = PQfnumber(res, "atttypdefn");
+ i_attlen = PQfnumber(res, "attlen");
+ i_attalign = PQfnumber(res, "attalign");
+ i_attisdropped = PQfnumber(res, "attisdropped");
+ i_attcollation = PQfnumber(res, "attcollation");
+
+ if (binary_upgrade)
+ {
+ binary_upgrade_set_type_oids_by_type_oid(fout, q,
+ tyinfo->dobj.catId.oid);
+ binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
+ }
+
+ qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
+
+ appendPQExpBuffer(q, "CREATE TYPE %s AS (",
+ qtypname);
+
+ actual_atts = 0;
+ for (i = 0; i < ntups; i++)
+ {
+ char *attname;
+ char *atttypdefn;
+ char *attlen;
+ char *attalign;
+ bool attisdropped;
+ Oid attcollation;
+
+ attname = PQgetvalue(res, i, i_attname);
+ atttypdefn = PQgetvalue(res, i, i_atttypdefn);
+ attlen = PQgetvalue(res, i, i_attlen);
+ attalign = PQgetvalue(res, i, i_attalign);
+ attisdropped = (PQgetvalue(res, i, i_attisdropped)[0] == 't');
+ attcollation = atooid(PQgetvalue(res, i, i_attcollation));
+
+ if (attisdropped && !binary_upgrade)
+ continue;
+
+ /* Format properly if not first attr */
+ if (actual_atts++ > 0)
+ appendPQExpBufferChar(q, ',');
+ appendPQExpBufferStr(q, "\n\t");
+
+ if (!attisdropped)
+ {
+ appendPQExpBuffer(q, "%s %s", fmtId(attname), atttypdefn);
+
+ /* Add collation if not default for the column type */
+ if (OidIsValid(attcollation))
+ {
+ CollInfo *coll;
+
+ coll = findCollationByOid(attcollation);
+ if (coll)
+ {
+ /* always schema-qualify, don't try to be smart */
+ appendPQExpBuffer(q, " COLLATE %s.",
+ fmtId(coll->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(coll->dobj.name));
+ }
+ }
+ }
+ else
+ {
+ /*
+ * This is a dropped attribute and we're in binary_upgrade mode.
+ * Insert a placeholder for it in the CREATE TYPE command, and set
+ * length and alignment with direct UPDATE to the catalogs
+ * afterwards. See similar code in dumpTableSchema().
+ */
+ appendPQExpBuffer(q, "%s INTEGER /* dummy */", fmtId(attname));
+
+ /* stash separately for insertion after the CREATE TYPE */
+ appendPQExpBufferStr(dropped,
+ "\n-- For binary upgrade, recreate dropped column.\n");
+ appendPQExpBuffer(dropped, "UPDATE pg_catalog.pg_attribute\n"
+ "SET attlen = %s, "
+ "attalign = '%s', attbyval = false\n"
+ "WHERE attname = ", attlen, attalign);
+ appendStringLiteralAH(dropped, attname, fout);
+ appendPQExpBufferStr(dropped, "\n AND attrelid = ");
+ appendStringLiteralAH(dropped, qtypname, fout);
+ appendPQExpBufferStr(dropped, "::pg_catalog.regclass;\n");
+
+ appendPQExpBuffer(dropped, "ALTER TYPE %s ",
+ qtypname);
+ appendPQExpBuffer(dropped, "DROP ATTRIBUTE %s;\n",
+ fmtId(attname));
+ }
+ }
+ appendPQExpBufferStr(q, "\n);\n");
+ appendPQExpBufferStr(q, dropped->data);
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP TYPE %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ qtypname);
+
+ appendPQExpBuffer(labelq, "TYPE %s", qtypname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tyinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
+ tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "TYPE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+
+ /* Dump Type Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tyinfo->dobj.namespace->dobj.name, tyinfo->rolname,
+ tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId);
+
+ dumpACL(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, "TYPE",
+ qtypname, NULL, tyinfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ tyinfo->rolname, tyinfo->typacl);
+
+ PQclear(res);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(dropped);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+
+ /* Dump any per-column comments */
+ dumpCompositeTypeColComments(fout, tyinfo);
+}
+
+/*
+ * dumpCompositeTypeColComments
+ * writes out to fout the queries to recreate comments on the columns of
+ * a user-defined stand-alone composite type
+ */
+static void
+dumpCompositeTypeColComments(Archive *fout, TypeInfo *tyinfo)
+{
+ CommentItem *comments;
+ int ncomments;
+ PGresult *res;
+ PQExpBuffer query;
+ PQExpBuffer target;
+ Oid pgClassOid;
+ int i;
+ int ntups;
+ int i_attname;
+ int i_attnum;
+
+ query = createPQExpBuffer();
+
+ /* We assume here that remoteVersion must be at least 70300 */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, a.attname, a.attnum "
+ "FROM pg_catalog.pg_class c, pg_catalog.pg_attribute a "
+ "WHERE c.oid = '%u' AND c.oid = a.attrelid "
+ " AND NOT a.attisdropped "
+ "ORDER BY a.attnum ",
+ tyinfo->typrelid);
+
+ /* Fetch column attnames */
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ if (ntups < 1)
+ {
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ return;
+ }
+
+ pgClassOid = atooid(PQgetvalue(res, 0, PQfnumber(res, "tableoid")));
+
+ /* Search for comments associated with type's pg_class OID */
+ ncomments = findComments(fout,
+ pgClassOid,
+ tyinfo->typrelid,
+ &comments);
+
+ /* If no comments exist, we're done */
+ if (ncomments <= 0)
+ {
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ return;
+ }
+
+ /* Build COMMENT ON statements */
+ target = createPQExpBuffer();
+
+ i_attnum = PQfnumber(res, "attnum");
+ i_attname = PQfnumber(res, "attname");
+ while (ncomments > 0)
+ {
+ const char *attname;
+
+ attname = NULL;
+ for (i = 0; i < ntups; i++)
+ {
+ if (atoi(PQgetvalue(res, i, i_attnum)) == comments->objsubid)
+ {
+ attname = PQgetvalue(res, i, i_attname);
+ break;
+ }
+ }
+ if (attname) /* just in case we don't find it */
+ {
+ const char *descr = comments->descr;
+
+ resetPQExpBuffer(target);
+ appendPQExpBuffer(target, "COLUMN %s.",
+ fmtId(tyinfo->dobj.name));
+ appendPQExpBufferStr(target, fmtId(attname));
+
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query, "COMMENT ON %s IS ", target->data);
+ appendStringLiteralAH(query, descr, fout);
+ appendPQExpBufferStr(query, ";\n");
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ target->data,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL, tyinfo->rolname,
+ false, "COMMENT", SECTION_NONE,
+ query->data, "", NULL,
+ &(tyinfo->dobj.dumpId), 1,
+ NULL, NULL);
+ }
+
+ comments++;
+ ncomments--;
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(target);
+}
+
+/*
+ * dumpShellType
+ * writes out to fout the queries to create a shell type
+ *
+ * We dump a shell definition in advance of the I/O functions for the type.
+ */
+static void
+dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
+{
+ PQExpBuffer q;
+
+ /* Skip if not to be dumped */
+ if (!stinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+
+ /*
+ * Note the lack of a DROP command for the shell type; any required DROP
+ * is driven off the base type entry, instead. This interacts with
+ * _printTocEntry()'s use of the presence of a DROP command to decide
+ * whether an entry needs an ALTER OWNER command. We don't want to alter
+ * the shell type's owner immediately on creation; that should happen only
+ * after it's filled in, otherwise the backend complains.
+ */
+
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_type_oid(fout, q,
+ stinfo->baseType->dobj.catId.oid);
+
+ appendPQExpBuffer(q, "CREATE TYPE %s;\n",
+ fmtId(stinfo->dobj.name));
+
+ ArchiveEntry(fout, stinfo->dobj.catId, stinfo->dobj.dumpId,
+ stinfo->dobj.name,
+ stinfo->dobj.namespace->dobj.name,
+ NULL,
+ stinfo->baseType->rolname, false,
+ "SHELL TYPE", SECTION_PRE_DATA,
+ q->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ destroyPQExpBuffer(q);
+}
+
+/*
+ * Determine whether we want to dump definitions for procedural languages.
+ * Since the languages themselves don't have schemas, we can't rely on
+ * the normal schema-based selection mechanism. We choose to dump them
+ * whenever neither --schema nor --table was given. (Before 8.1, we used
+ * the dump flag of the PL's call handler function, but in 8.1 this will
+ * probably always be false since call handlers are created in pg_catalog.)
+ *
+ * For some backwards compatibility with the older behavior, we forcibly
+ * dump a PL if its handler function (and validator if any) are in a
+ * dumpable namespace. That case is not checked here.
+ *
+ * Also, if the PL belongs to an extension, we do not use this heuristic.
+ * That case isn't checked here either.
+ */
+static bool
+shouldDumpProcLangs(void)
+{
+ if (!include_everything)
+ return false;
+ /* And they're schema not data */
+ if (dataOnly)
+ return false;
+ return true;
+}
+
+/*
+ * dumpProcLang
+ * writes out to fout the queries to recreate a user-defined
+ * procedural language
+ */
+static void
+dumpProcLang(Archive *fout, ProcLangInfo *plang)
+{
+ PQExpBuffer defqry;
+ PQExpBuffer delqry;
+ PQExpBuffer labelq;
+ bool useParams;
+ char *qlanname;
+ char *lanschema;
+ FuncInfo *funcInfo;
+ FuncInfo *inlineInfo = NULL;
+ FuncInfo *validatorInfo = NULL;
+
+ /* Skip if not to be dumped */
+ if (!plang->dobj.dump || dataOnly)
+ return;
+
+ /*
+ * Try to find the support function(s). It is not an error if we don't
+ * find them --- if the functions are in the pg_catalog schema, as is
+ * standard in 8.1 and up, then we won't have loaded them. (In this case
+ * we will emit a parameterless CREATE LANGUAGE command, which will
+ * require PL template knowledge in the backend to reload.)
+ */
+
+ funcInfo = findFuncByOid(plang->lanplcallfoid);
+ if (funcInfo != NULL && !funcInfo->dobj.dump)
+ funcInfo = NULL; /* treat not-dumped same as not-found */
+
+ if (OidIsValid(plang->laninline))
+ {
+ inlineInfo = findFuncByOid(plang->laninline);
+ if (inlineInfo != NULL && !inlineInfo->dobj.dump)
+ inlineInfo = NULL;
+ }
+
+ if (OidIsValid(plang->lanvalidator))
+ {
+ validatorInfo = findFuncByOid(plang->lanvalidator);
+ if (validatorInfo != NULL && !validatorInfo->dobj.dump)
+ validatorInfo = NULL;
+ }
+
+ /*
+ * If the functions are dumpable then emit a traditional CREATE LANGUAGE
+ * with parameters. Otherwise, dump only if shouldDumpProcLangs() says to
+ * dump it.
+ *
+ * However, for a language that belongs to an extension, we must not use
+ * the shouldDumpProcLangs heuristic, but just dump the language iff we're
+ * told to (via dobj.dump). Generally the support functions will belong
+ * to the same extension and so have the same dump flags ... if they
+ * don't, this might not work terribly nicely.
+ */
+ useParams = (funcInfo != NULL &&
+ (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
+ (validatorInfo != NULL || !OidIsValid(plang->lanvalidator)));
+
+ if (!plang->dobj.ext_member)
+ {
+ if (!useParams && !shouldDumpProcLangs())
+ return;
+ }
+
+ defqry = createPQExpBuffer();
+ delqry = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ qlanname = pg_strdup(fmtId(plang->dobj.name));
+
+ /*
+ * If dumping a HANDLER clause, treat the language as being in the handler
+ * function's schema; this avoids cluttering the HANDLER clause. Otherwise
+ * it doesn't really have a schema.
+ */
+ if (useParams)
+ lanschema = funcInfo->dobj.namespace->dobj.name;
+ else
+ lanschema = NULL;
+
+ appendPQExpBuffer(delqry, "DROP PROCEDURAL LANGUAGE %s;\n",
+ qlanname);
+
+ if (useParams)
+ {
+ appendPQExpBuffer(defqry, "CREATE %sPROCEDURAL LANGUAGE %s",
+ plang->lanpltrusted ? "TRUSTED " : "",
+ qlanname);
+ appendPQExpBuffer(defqry, " HANDLER %s",
+ fmtId(funcInfo->dobj.name));
+ if (OidIsValid(plang->laninline))
+ {
+ appendPQExpBufferStr(defqry, " INLINE ");
+ /* Cope with possibility that inline is in different schema */
+ if (inlineInfo->dobj.namespace != funcInfo->dobj.namespace)
+ appendPQExpBuffer(defqry, "%s.",
+ fmtId(inlineInfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(defqry, fmtId(inlineInfo->dobj.name));
+ }
+ if (OidIsValid(plang->lanvalidator))
+ {
+ appendPQExpBufferStr(defqry, " VALIDATOR ");
+ /* Cope with possibility that validator is in different schema */
+ if (validatorInfo->dobj.namespace != funcInfo->dobj.namespace)
+ appendPQExpBuffer(defqry, "%s.",
+ fmtId(validatorInfo->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(defqry, fmtId(validatorInfo->dobj.name));
+ }
+ }
+ else
+ {
+ /*
+ * If not dumping parameters, then use CREATE OR REPLACE so that the
+ * command will not fail if the language is preinstalled in the target
+ * database. We restrict the use of REPLACE to this case so as to
+ * eliminate the risk of replacing a language with incompatible
+ * parameter settings: this command will only succeed at all if there
+ * is a pg_pltemplate entry, and if there is one, the existing entry
+ * must match it too.
+ */
+ appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
+ qlanname);
+ }
+ appendPQExpBufferStr(defqry, ";\n");
+
+ appendPQExpBuffer(labelq, "LANGUAGE %s", qlanname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(defqry, &plang->dobj, labelq->data);
+
+ ArchiveEntry(fout, plang->dobj.catId, plang->dobj.dumpId,
+ plang->dobj.name,
+ lanschema, NULL, plang->lanowner,
+ false, "PROCEDURAL LANGUAGE", SECTION_PRE_DATA,
+ defqry->data, delqry->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Proc Lang Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ plang->dobj.catId, 0, plang->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ NULL, "",
+ plang->dobj.catId, 0, plang->dobj.dumpId);
+
+ if (plang->lanpltrusted)
+ dumpACL(fout, plang->dobj.catId, plang->dobj.dumpId, "LANGUAGE",
+ qlanname, NULL, plang->dobj.name,
+ lanschema,
+ plang->lanowner, plang->lanacl);
+
+ free(qlanname);
+
+ destroyPQExpBuffer(defqry);
+ destroyPQExpBuffer(delqry);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * format_function_arguments: generate function name and argument list
+ *
+ * This is used when we can rely on pg_get_function_arguments to format
+ * the argument list. Note, however, that pg_get_function_arguments
+ * does not special-case zero-argument aggregates.
+ */
+static char *
+format_function_arguments(FuncInfo *finfo, char *funcargs, bool is_agg)
+{
+ PQExpBufferData fn;
+
+ initPQExpBuffer(&fn);
+ appendPQExpBufferStr(&fn, fmtId(finfo->dobj.name));
+ if (is_agg && finfo->nargs == 0)
+ appendPQExpBufferStr(&fn, "(*)");
+ else
+ appendPQExpBuffer(&fn, "(%s)", funcargs);
+ return fn.data;
+}
+
+/*
+ * format_function_arguments_old: generate function name and argument list
+ *
+ * The argument type names are qualified if needed. The function name
+ * is never qualified.
+ *
+ * This is used only with pre-8.4 servers, so we aren't expecting to see
+ * VARIADIC or TABLE arguments, nor are there any defaults for arguments.
+ *
+ * Any or all of allargtypes, argmodes, argnames may be NULL.
+ */
+static char *
+format_function_arguments_old(Archive *fout,
+ FuncInfo *finfo, int nallargs,
+ char **allargtypes,
+ char **argmodes,
+ char **argnames)
+{
+ PQExpBufferData fn;
+ int j;
+
+ initPQExpBuffer(&fn);
+ appendPQExpBuffer(&fn, "%s(", fmtId(finfo->dobj.name));
+ for (j = 0; j < nallargs; j++)
+ {
+ Oid typid;
+ char *typname;
+ const char *argmode;
+ const char *argname;
+
+ typid = allargtypes ? atooid(allargtypes[j]) : finfo->argtypes[j];
+ typname = getFormattedTypeName(fout, typid, zeroAsOpaque);
+
+ if (argmodes)
+ {
+ switch (argmodes[j][0])
+ {
+ case PROARGMODE_IN:
+ argmode = "";
+ break;
+ case PROARGMODE_OUT:
+ argmode = "OUT ";
+ break;
+ case PROARGMODE_INOUT:
+ argmode = "INOUT ";
+ break;
+ default:
+ write_msg(NULL, "WARNING: bogus value in proargmodes array\n");
+ argmode = "";
+ break;
+ }
+ }
+ else
+ argmode = "";
+
+ argname = argnames ? argnames[j] : (char *) NULL;
+ if (argname && argname[0] == '\0')
+ argname = NULL;
+
+ appendPQExpBuffer(&fn, "%s%s%s%s%s",
+ (j > 0) ? ", " : "",
+ argmode,
+ argname ? fmtId(argname) : "",
+ argname ? " " : "",
+ typname);
+ free(typname);
+ }
+ appendPQExpBufferChar(&fn, ')');
+ return fn.data;
+}
+
+/*
+ * format_function_signature: generate function name and argument list
+ *
+ * This is like format_function_arguments_old except that only a minimal
+ * list of input argument types is generated; this is sufficient to
+ * reference the function, but not to define it.
+ *
+ * If honor_quotes is false then the function name is never quoted.
+ * This is appropriate for use in TOC tags, but not in SQL commands.
+ */
+static char *
+format_function_signature(Archive *fout, FuncInfo *finfo, bool honor_quotes)
+{
+ PQExpBufferData fn;
+ int j;
+
+ initPQExpBuffer(&fn);
+ if (honor_quotes)
+ appendPQExpBuffer(&fn, "%s(", fmtId(finfo->dobj.name));
+ else
+ appendPQExpBuffer(&fn, "%s(", finfo->dobj.name);
+ for (j = 0; j < finfo->nargs; j++)
+ {
+ char *typname;
+
+ if (j > 0)
+ appendPQExpBufferStr(&fn, ", ");
+
+ typname = getFormattedTypeName(fout, finfo->argtypes[j],
+ zeroAsOpaque);
+ appendPQExpBufferStr(&fn, typname);
+ free(typname);
+ }
+ appendPQExpBufferChar(&fn, ')');
+ return fn.data;
+}
+
+
+/*
+ * dumpFunc:
+ * dump out one function
+ */
+static void
+dumpFunc(Archive *fout, FuncInfo *finfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delqry;
+ PQExpBuffer labelq;
+ PQExpBuffer asPart;
+ PGresult *res;
+ char *funcsig; /* identity signature */
+ char *funcfullsig = NULL; /* full signature */
+ char *funcsig_tag;
+ char *proretset;
+ char *prosrc;
+ char *probin;
+ char *funcargs;
+ char *funciargs;
+ char *funcresult;
+ char *proallargtypes;
+ char *proargmodes;
+ char *proargnames;
+ char *proiswindow;
+ char *provolatile;
+ char *proisstrict;
+ char *prosecdef;
+ char *proleakproof;
+ char *proconfig;
+ char *procost;
+ char *prorows;
+ char *lanname;
+ char *rettypename;
+ int nallargs;
+ char **allargtypes = NULL;
+ char **argmodes = NULL;
+ char **argnames = NULL;
+ char **configitems = NULL;
+ int nconfigitems = 0;
+ int i;
+
+ /* Skip if not to be dumped */
+ if (!finfo->dobj.dump || dataOnly)
+ return;
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delqry = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+ asPart = createPQExpBuffer();
+
+ /* Set proper schema search path so type references list correctly */
+ selectSourceSchema(fout, finfo->dobj.namespace->dobj.name);
+
+ /* Fetch function-specific details */
+ if (fout->remoteVersion >= 90200)
+ {
+ /*
+ * proleakproof was added at v9.2
+ */
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
+ "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
+ "pg_catalog.pg_get_function_result(oid) AS funcresult, "
+ "proiswindow, provolatile, proisstrict, prosecdef, "
+ "proleakproof, proconfig, procost, prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ finfo->dobj.catId.oid);
+ }
+ else 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.
+ */
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
+ "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
+ "pg_catalog.pg_get_function_result(oid) AS funcresult, "
+ "proiswindow, provolatile, proisstrict, prosecdef, "
+ "false AS proleakproof, "
+ " proconfig, procost, prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ finfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "proallargtypes, proargmodes, proargnames, "
+ "false AS proiswindow, "
+ "provolatile, proisstrict, prosecdef, "
+ "false AS proleakproof, "
+ "proconfig, procost, prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ finfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80100)
+ {
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "proallargtypes, proargmodes, proargnames, "
+ "false AS proiswindow, "
+ "provolatile, proisstrict, prosecdef, "
+ "false AS proleakproof, "
+ "null AS proconfig, 0 AS procost, 0 AS prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ finfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80000)
+ {
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "null AS proallargtypes, "
+ "null AS proargmodes, "
+ "proargnames, "
+ "false AS proiswindow, "
+ "provolatile, proisstrict, prosecdef, "
+ "false AS proleakproof, "
+ "null AS proconfig, 0 AS procost, 0 AS prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ finfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "null AS proallargtypes, "
+ "null AS proargmodes, "
+ "null AS proargnames, "
+ "false AS proiswindow, "
+ "provolatile, proisstrict, prosecdef, "
+ "false AS proleakproof, "
+ "null AS proconfig, 0 AS procost, 0 AS prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ finfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "null AS proallargtypes, "
+ "null AS proargmodes, "
+ "null AS proargnames, "
+ "false AS proiswindow, "
+ "case when proiscachable then 'i' else 'v' end AS provolatile, "
+ "proisstrict, "
+ "false AS prosecdef, "
+ "false AS proleakproof, "
+ "null AS proconfig, 0 AS procost, 0 AS prorows, "
+ "(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_proc "
+ "WHERE oid = '%u'::oid",
+ finfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query,
+ "SELECT proretset, prosrc, probin, "
+ "null AS proallargtypes, "
+ "null AS proargmodes, "
+ "null AS proargnames, "
+ "false AS proiswindow, "
+ "CASE WHEN proiscachable THEN 'i' ELSE 'v' END AS provolatile, "
+ "false AS proisstrict, "
+ "false AS prosecdef, "
+ "false AS proleakproof, "
+ "NULL AS proconfig, 0 AS procost, 0 AS prorows, "
+ "(SELECT lanname FROM pg_language WHERE oid = prolang) AS lanname "
+ "FROM pg_proc "
+ "WHERE oid = '%u'::oid",
+ finfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (fout->remoteVersion >= 80400)
+ {
+ funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
+ funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
+ funcresult = PQgetvalue(res, 0, PQfnumber(res, "funcresult"));
+ proallargtypes = proargmodes = proargnames = NULL;
+ }
+ else
+ {
+ proallargtypes = PQgetvalue(res, 0, PQfnumber(res, "proallargtypes"));
+ proargmodes = PQgetvalue(res, 0, PQfnumber(res, "proargmodes"));
+ proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
+ funcargs = funciargs = funcresult = NULL;
+ }
+ proiswindow = PQgetvalue(res, 0, PQfnumber(res, "proiswindow"));
+ provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
+ proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
+ prosecdef = PQgetvalue(res, 0, PQfnumber(res, "prosecdef"));
+ proleakproof = PQgetvalue(res, 0, PQfnumber(res, "proleakproof"));
+ proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig"));
+ procost = PQgetvalue(res, 0, PQfnumber(res, "procost"));
+ prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows"));
+ lanname = PQgetvalue(res, 0, PQfnumber(res, "lanname"));
+
+ /*
+ * See backend/commands/functioncmds.c for details of how the 'AS' clause
+ * is used. In 8.4 and up, an unused probin is NULL (here ""); previous
+ * versions would set it to "-". There are no known cases in which prosrc
+ * is unused, so the tests below for "-" are probably useless.
+ */
+ if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ {
+ appendPQExpBufferStr(asPart, "AS ");
+ appendStringLiteralAH(asPart, probin, fout);
+ if (strcmp(prosrc, "-") != 0)
+ {
+ appendPQExpBufferStr(asPart, ", ");
+
+ /*
+ * where we have bin, use dollar quoting if allowed and src
+ * contains quote or backslash; else use regular quoting.
+ */
+ if (disable_dollar_quoting ||
+ (strchr(prosrc, '\'') == NULL && strchr(prosrc, '\\') == NULL))
+ appendStringLiteralAH(asPart, prosrc, fout);
+ else
+ appendStringLiteralDQ(asPart, prosrc, NULL);
+ }
+ }
+ else
+ {
+ if (strcmp(prosrc, "-") != 0)
+ {
+ appendPQExpBufferStr(asPart, "AS ");
+ /* with no bin, dollar quote src unconditionally if allowed */
+ if (disable_dollar_quoting)
+ appendStringLiteralAH(asPart, prosrc, fout);
+ else
+ appendStringLiteralDQ(asPart, prosrc, NULL);
+ }
+ }
+
+ nallargs = finfo->nargs; /* unless we learn different from allargs */
+
+ if (proallargtypes && *proallargtypes)
+ {
+ int nitems = 0;
+
+ if (!parsePGArray(proallargtypes, &allargtypes, &nitems) ||
+ nitems < finfo->nargs)
+ {
+ write_msg(NULL, "WARNING: could not parse proallargtypes array\n");
+ if (allargtypes)
+ free(allargtypes);
+ allargtypes = NULL;
+ }
+ else
+ nallargs = nitems;
+ }
+
+ if (proargmodes && *proargmodes)
+ {
+ int nitems = 0;
+
+ if (!parsePGArray(proargmodes, &argmodes, &nitems) ||
+ nitems != nallargs)
+ {
+ write_msg(NULL, "WARNING: could not parse proargmodes array\n");
+ if (argmodes)
+ free(argmodes);
+ argmodes = NULL;
+ }
+ }
+
+ if (proargnames && *proargnames)
+ {
+ int nitems = 0;
+
+ if (!parsePGArray(proargnames, &argnames, &nitems) ||
+ nitems != nallargs)
+ {
+ write_msg(NULL, "WARNING: could not parse proargnames array\n");
+ if (argnames)
+ free(argnames);
+ argnames = NULL;
+ }
+ }
+
+ if (proconfig && *proconfig)
+ {
+ if (!parsePGArray(proconfig, &configitems, &nconfigitems))
+ {
+ write_msg(NULL, "WARNING: could not parse proconfig array\n");
+ if (configitems)
+ free(configitems);
+ configitems = NULL;
+ nconfigitems = 0;
+ }
+ }
+
+ if (funcargs)
+ {
+ /* 8.4 or later; we rely on server-side code for most of the work */
+ funcfullsig = format_function_arguments(finfo, funcargs, false);
+ funcsig = format_function_arguments(finfo, funciargs, false);
+ }
+ else
+ /* pre-8.4, do it ourselves */
+ funcsig = format_function_arguments_old(fout,
+ finfo, nallargs, allargtypes,
+ argmodes, argnames);
+
+ funcsig_tag = format_function_signature(fout, finfo, false);
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delqry, "DROP FUNCTION %s.%s;\n",
+ fmtId(finfo->dobj.namespace->dobj.name),
+ funcsig);
+
+ appendPQExpBuffer(q, "CREATE FUNCTION %s ", funcfullsig ? funcfullsig :
+ funcsig);
+ if (funcresult)
+ appendPQExpBuffer(q, "RETURNS %s", funcresult);
+ else
+ {
+ rettypename = getFormattedTypeName(fout, finfo->prorettype,
+ zeroAsOpaque);
+ appendPQExpBuffer(q, "RETURNS %s%s",
+ (proretset[0] == 't') ? "SETOF " : "",
+ rettypename);
+ free(rettypename);
+ }
+
+ appendPQExpBuffer(q, "\n LANGUAGE %s", fmtId(lanname));
+
+ if (proiswindow[0] == 't')
+ appendPQExpBufferStr(q, " WINDOW");
+
+ if (provolatile[0] != PROVOLATILE_VOLATILE)
+ {
+ if (provolatile[0] == PROVOLATILE_IMMUTABLE)
+ appendPQExpBufferStr(q, " IMMUTABLE");
+ else if (provolatile[0] == PROVOLATILE_STABLE)
+ appendPQExpBufferStr(q, " STABLE");
+ else if (provolatile[0] != PROVOLATILE_VOLATILE)
+ exit_horribly(NULL, "unrecognized provolatile value for function \"%s\"\n",
+ finfo->dobj.name);
+ }
+
+ if (proisstrict[0] == 't')
+ appendPQExpBufferStr(q, " STRICT");
+
+ if (prosecdef[0] == 't')
+ appendPQExpBufferStr(q, " SECURITY DEFINER");
+
+ if (proleakproof[0] == 't')
+ appendPQExpBufferStr(q, " LEAKPROOF");
+
+ /*
+ * COST and ROWS are emitted only if present and not default, so as not to
+ * break backwards-compatibility of the dump without need. Keep this code
+ * in sync with the defaults in functioncmds.c.
+ */
+ if (strcmp(procost, "0") != 0)
+ {
+ if (strcmp(lanname, "internal") == 0 || strcmp(lanname, "c") == 0)
+ {
+ /* default cost is 1 */
+ if (strcmp(procost, "1") != 0)
+ appendPQExpBuffer(q, " COST %s", procost);
+ }
+ else
+ {
+ /* default cost is 100 */
+ if (strcmp(procost, "100") != 0)
+ appendPQExpBuffer(q, " COST %s", procost);
+ }
+ }
+ if (proretset[0] == 't' &&
+ strcmp(prorows, "0") != 0 && strcmp(prorows, "1000") != 0)
+ appendPQExpBuffer(q, " ROWS %s", prorows);
+
+ for (i = 0; i < nconfigitems; i++)
+ {
+ /* we feel free to scribble on configitems[] here */
+ char *configitem = configitems[i];
+ char *pos;
+
+ pos = strchr(configitem, '=');
+ if (pos == NULL)
+ continue;
+ *pos++ = '\0';
+ appendPQExpBuffer(q, "\n SET %s TO ", fmtId(configitem));
+
+ /*
+ * Some GUC variable names are 'LIST' type and hence must not be
+ * quoted.
+ */
+ if (pg_strcasecmp(configitem, "DateStyle") == 0
+ || pg_strcasecmp(configitem, "search_path") == 0)
+ appendPQExpBufferStr(q, pos);
+ else
+ appendStringLiteralAH(q, pos, fout);
+ }
+
+ appendPQExpBuffer(q, "\n %s;\n", asPart->data);
+
+ appendPQExpBuffer(labelq, "FUNCTION %s", funcsig);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &finfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, finfo->dobj.catId, finfo->dobj.dumpId,
+ funcsig_tag,
+ finfo->dobj.namespace->dobj.name,
+ NULL,
+ finfo->rolname, false,
+ "FUNCTION", SECTION_PRE_DATA,
+ q->data, delqry->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Function Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ finfo->dobj.namespace->dobj.name, finfo->rolname,
+ finfo->dobj.catId, 0, finfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ finfo->dobj.namespace->dobj.name, finfo->rolname,
+ finfo->dobj.catId, 0, finfo->dobj.dumpId);
+
+ dumpACL(fout, finfo->dobj.catId, finfo->dobj.dumpId, "FUNCTION",
+ funcsig, NULL, funcsig_tag,
+ finfo->dobj.namespace->dobj.name,
+ finfo->rolname, finfo->proacl);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delqry);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(asPart);
+ free(funcsig);
+ if (funcfullsig)
+ free(funcfullsig);
+ free(funcsig_tag);
+ if (allargtypes)
+ free(allargtypes);
+ if (argmodes)
+ free(argmodes);
+ if (argnames)
+ free(argnames);
+ if (configitems)
+ free(configitems);
+}
+
+
+/*
+ * Dump a user-defined cast
+ */
+static void
+dumpCast(Archive *fout, CastInfo *cast)
+{
+ PQExpBuffer defqry;
+ PQExpBuffer delqry;
+ PQExpBuffer labelq;
+ FuncInfo *funcInfo = NULL;
+
+ /* Skip if not to be dumped */
+ if (!cast->dobj.dump || dataOnly)
+ return;
+
+ /* Cannot dump if we don't have the cast function's info */
+ if (OidIsValid(cast->castfunc))
+ {
+ funcInfo = findFuncByOid(cast->castfunc);
+ if (funcInfo == NULL)
+ return;
+ }
+
+ /*
+ * Make sure we are in proper schema (needed for getFormattedTypeName).
+ * Casts don't have a schema of their own, so use pg_catalog.
+ */
+ selectSourceSchema(fout, "pg_catalog");
+
+ defqry = createPQExpBuffer();
+ delqry = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ appendPQExpBuffer(delqry, "DROP CAST (%s AS %s);\n",
+ getFormattedTypeName(fout, cast->castsource, zeroAsNone),
+ getFormattedTypeName(fout, cast->casttarget, zeroAsNone));
+
+ appendPQExpBuffer(defqry, "CREATE CAST (%s AS %s) ",
+ getFormattedTypeName(fout, cast->castsource, zeroAsNone),
+ getFormattedTypeName(fout, cast->casttarget, zeroAsNone));
+
+ switch (cast->castmethod)
+ {
+ case COERCION_METHOD_BINARY:
+ appendPQExpBufferStr(defqry, "WITHOUT FUNCTION");
+ break;
+ case COERCION_METHOD_INOUT:
+ appendPQExpBufferStr(defqry, "WITH INOUT");
+ break;
+ case COERCION_METHOD_FUNCTION:
+ if (funcInfo)
+ {
+ char *fsig = format_function_signature(fout, funcInfo, true);
+
+ /*
+ * Always qualify the function name, in case it is not in
+ * pg_catalog schema (format_function_signature won't qualify
+ * it).
+ */
+ appendPQExpBuffer(defqry, "WITH FUNCTION %s.%s",
+ fmtId(funcInfo->dobj.namespace->dobj.name), fsig);
+ free(fsig);
+ }
+ else
+ write_msg(NULL, "WARNING: bogus value in pg_cast.castfunc or pg_cast.castmethod field\n");
+ break;
+ default:
+ write_msg(NULL, "WARNING: bogus value in pg_cast.castmethod field\n");
+ }
+
+ if (cast->castcontext == 'a')
+ appendPQExpBufferStr(defqry, " AS ASSIGNMENT");
+ else if (cast->castcontext == 'i')
+ appendPQExpBufferStr(defqry, " AS IMPLICIT");
+ appendPQExpBufferStr(defqry, ";\n");
+
+ appendPQExpBuffer(labelq, "CAST (%s AS %s)",
+ getFormattedTypeName(fout, cast->castsource, zeroAsNone),
+ getFormattedTypeName(fout, cast->casttarget, zeroAsNone));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(defqry, &cast->dobj, labelq->data);
+
+ ArchiveEntry(fout, cast->dobj.catId, cast->dobj.dumpId,
+ labelq->data,
+ "pg_catalog", NULL, "",
+ false, "CAST", SECTION_PRE_DATA,
+ defqry->data, delqry->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Cast Comments */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ cast->dobj.catId, 0, cast->dobj.dumpId);
+
+ destroyPQExpBuffer(defqry);
+ destroyPQExpBuffer(delqry);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpOpr
+ * write out a single operator definition
+ */
+static void
+dumpOpr(Archive *fout, OprInfo *oprinfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PQExpBuffer oprid;
+ PQExpBuffer details;
+ const char *name;
+ PGresult *res;
+ int i_oprkind;
+ int i_oprcode;
+ int i_oprleft;
+ int i_oprright;
+ int i_oprcom;
+ int i_oprnegate;
+ int i_oprrest;
+ int i_oprjoin;
+ int i_oprcanmerge;
+ int i_oprcanhash;
+ char *oprkind;
+ char *oprcode;
+ char *oprleft;
+ char *oprright;
+ char *oprcom;
+ char *oprnegate;
+ char *oprrest;
+ char *oprjoin;
+ char *oprcanmerge;
+ char *oprcanhash;
+ char *oprregproc;
+ char *oprref;
+
+ /* Skip if not to be dumped */
+ if (!oprinfo->dobj.dump || dataOnly)
+ return;
+
+ /*
+ * some operators are invalid because they were the result of user
+ * defining operators before commutators exist
+ */
+ if (!OidIsValid(oprinfo->oprcode))
+ return;
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+ oprid = createPQExpBuffer();
+ details = createPQExpBuffer();
+
+ /* Make sure we are in proper schema so regoperator works correctly */
+ selectSourceSchema(fout, oprinfo->dobj.namespace->dobj.name);
+
+ if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBuffer(query, "SELECT oprkind, "
+ "oprcode::pg_catalog.regprocedure, "
+ "oprleft::pg_catalog.regtype, "
+ "oprright::pg_catalog.regtype, "
+ "oprcom::pg_catalog.regoperator, "
+ "oprnegate::pg_catalog.regoperator, "
+ "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->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT oprkind, "
+ "oprcode::pg_catalog.regprocedure, "
+ "oprleft::pg_catalog.regtype, "
+ "oprright::pg_catalog.regtype, "
+ "oprcom::pg_catalog.regoperator, "
+ "oprnegate::pg_catalog.regoperator, "
+ "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);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT oprkind, oprcode, "
+ "CASE WHEN oprleft = 0 THEN '-' "
+ "ELSE format_type(oprleft, NULL) END AS oprleft, "
+ "CASE WHEN oprright = 0 THEN '-' "
+ "ELSE format_type(oprright, NULL) END AS oprright, "
+ "oprcom, oprnegate, oprrest, oprjoin, "
+ "(oprlsortop != 0) AS oprcanmerge, "
+ "oprcanhash "
+ "FROM pg_operator "
+ "WHERE oid = '%u'::oid",
+ oprinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT oprkind, oprcode, "
+ "CASE WHEN oprleft = 0 THEN '-'::name "
+ "ELSE (SELECT typname FROM pg_type WHERE oid = oprleft) END AS oprleft, "
+ "CASE WHEN oprright = 0 THEN '-'::name "
+ "ELSE (SELECT typname FROM pg_type WHERE oid = oprright) END AS oprright, "
+ "oprcom, oprnegate, oprrest, oprjoin, "
+ "(oprlsortop != 0) AS oprcanmerge, "
+ "oprcanhash "
+ "FROM pg_operator "
+ "WHERE oid = '%u'::oid",
+ oprinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ i_oprkind = PQfnumber(res, "oprkind");
+ i_oprcode = PQfnumber(res, "oprcode");
+ i_oprleft = PQfnumber(res, "oprleft");
+ i_oprright = PQfnumber(res, "oprright");
+ i_oprcom = PQfnumber(res, "oprcom");
+ i_oprnegate = PQfnumber(res, "oprnegate");
+ i_oprrest = PQfnumber(res, "oprrest");
+ i_oprjoin = PQfnumber(res, "oprjoin");
+ i_oprcanmerge = PQfnumber(res, "oprcanmerge");
+ i_oprcanhash = PQfnumber(res, "oprcanhash");
+
+ oprkind = PQgetvalue(res, 0, i_oprkind);
+ oprcode = PQgetvalue(res, 0, i_oprcode);
+ oprleft = PQgetvalue(res, 0, i_oprleft);
+ oprright = PQgetvalue(res, 0, i_oprright);
+ oprcom = PQgetvalue(res, 0, i_oprcom);
+ oprnegate = PQgetvalue(res, 0, i_oprnegate);
+ oprrest = PQgetvalue(res, 0, i_oprrest);
+ oprjoin = PQgetvalue(res, 0, i_oprjoin);
+ oprcanmerge = PQgetvalue(res, 0, i_oprcanmerge);
+ oprcanhash = PQgetvalue(res, 0, i_oprcanhash);
+
+ oprregproc = convertRegProcReference(fout, oprcode);
+ if (oprregproc)
+ {
+ appendPQExpBuffer(details, " PROCEDURE = %s", oprregproc);
+ free(oprregproc);
+ }
+
+ appendPQExpBuffer(oprid, "%s (",
+ oprinfo->dobj.name);
+
+ /*
+ * right unary means there's a left arg and left unary means there's a
+ * right arg
+ */
+ if (strcmp(oprkind, "r") == 0 ||
+ strcmp(oprkind, "b") == 0)
+ {
+ if (fout->remoteVersion >= 70100)
+ name = oprleft;
+ else
+ name = fmtId(oprleft);
+ appendPQExpBuffer(details, ",\n LEFTARG = %s", name);
+ appendPQExpBufferStr(oprid, name);
+ }
+ else
+ appendPQExpBufferStr(oprid, "NONE");
+
+ if (strcmp(oprkind, "l") == 0 ||
+ strcmp(oprkind, "b") == 0)
+ {
+ if (fout->remoteVersion >= 70100)
+ name = oprright;
+ else
+ name = fmtId(oprright);
+ appendPQExpBuffer(details, ",\n RIGHTARG = %s", name);
+ appendPQExpBuffer(oprid, ", %s)", name);
+ }
+ else
+ appendPQExpBufferStr(oprid, ", NONE)");
+
+ oprref = convertOperatorReference(fout, oprcom);
+ if (oprref)
+ {
+ appendPQExpBuffer(details, ",\n COMMUTATOR = %s", oprref);
+ free(oprref);
+ }
+
+ oprref = convertOperatorReference(fout, oprnegate);
+ if (oprref)
+ {
+ appendPQExpBuffer(details, ",\n NEGATOR = %s", oprref);
+ free(oprref);
+ }
+
+ if (strcmp(oprcanmerge, "t") == 0)
+ appendPQExpBufferStr(details, ",\n MERGES");
+
+ if (strcmp(oprcanhash, "t") == 0)
+ appendPQExpBufferStr(details, ",\n HASHES");
+
+ oprregproc = convertRegProcReference(fout, oprrest);
+ if (oprregproc)
+ {
+ appendPQExpBuffer(details, ",\n RESTRICT = %s", oprregproc);
+ free(oprregproc);
+ }
+
+ oprregproc = convertRegProcReference(fout, oprjoin);
+ if (oprregproc)
+ {
+ appendPQExpBuffer(details, ",\n JOIN = %s", oprregproc);
+ free(oprregproc);
+ }
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP OPERATOR %s.%s;\n",
+ fmtId(oprinfo->dobj.namespace->dobj.name),
+ oprid->data);
+
+ appendPQExpBuffer(q, "CREATE OPERATOR %s (\n%s\n);\n",
+ oprinfo->dobj.name, details->data);
+
+ appendPQExpBuffer(labelq, "OPERATOR %s", oprid->data);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &oprinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, oprinfo->dobj.catId, oprinfo->dobj.dumpId,
+ oprinfo->dobj.name,
+ oprinfo->dobj.namespace->dobj.name,
+ NULL,
+ oprinfo->rolname,
+ false, "OPERATOR", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Operator Comments */
+ dumpComment(fout, labelq->data,
+ oprinfo->dobj.namespace->dobj.name, oprinfo->rolname,
+ oprinfo->dobj.catId, 0, oprinfo->dobj.dumpId);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(oprid);
+ destroyPQExpBuffer(details);
+}
+
+/*
+ * Convert a function reference obtained from pg_operator
+ *
+ * Returns allocated string of what to print, or NULL if function references
+ * is InvalidOid. Returned string is expected to be free'd by the caller.
+ *
+ * In 7.3 the input is a REGPROCEDURE display; we have to strip the
+ * argument-types part. In prior versions, the input is a REGPROC display.
+ */
+static char *
+convertRegProcReference(Archive *fout, const char *proc)
+{
+ /* In all cases "-" means a null reference */
+ if (strcmp(proc, "-") == 0)
+ return NULL;
+
+ if (fout->remoteVersion >= 70300)
+ {
+ char *name;
+ char *paren;
+ bool inquote;
+
+ name = pg_strdup(proc);
+ /* find non-double-quoted left paren */
+ inquote = false;
+ for (paren = name; *paren; paren++)
+ {
+ if (*paren == '(' && !inquote)
+ {
+ *paren = '\0';
+ break;
+ }
+ if (*paren == '"')
+ inquote = !inquote;
+ }
+ return name;
+ }
+
+ /* REGPROC before 7.3 does not quote its result */
+ return pg_strdup(fmtId(proc));
+}
+
+/*
+ * Convert an operator cross-reference obtained from pg_operator
+ *
+ * Returns an allocated string of what to print, or NULL to print nothing.
+ * Caller is responsible for free'ing result string.
+ *
+ * In 7.3 and up the input is a REGOPERATOR display; we have to strip the
+ * argument-types part, and add OPERATOR() decoration if the name is
+ * schema-qualified. In older versions, the input is just a numeric OID,
+ * which we search our operator list for.
+ */
+static char *
+convertOperatorReference(Archive *fout, const char *opr)
+{
+ OprInfo *oprInfo;
+
+ /* In all cases "0" means a null reference */
+ if (strcmp(opr, "0") == 0)
+ return NULL;
+
+ if (fout->remoteVersion >= 70300)
+ {
+ char *name;
+ char *oname;
+ char *ptr;
+ bool inquote;
+ bool sawdot;
+
+ name = pg_strdup(opr);
+ /* find non-double-quoted left paren, and check for non-quoted dot */
+ inquote = false;
+ sawdot = false;
+ for (ptr = name; *ptr; ptr++)
+ {
+ if (*ptr == '"')
+ inquote = !inquote;
+ else if (*ptr == '.' && !inquote)
+ sawdot = true;
+ else if (*ptr == '(' && !inquote)
+ {
+ *ptr = '\0';
+ break;
+ }
+ }
+ /* If not schema-qualified, don't need to add OPERATOR() */
+ if (!sawdot)
+ return name;
+ oname = psprintf("OPERATOR(%s)", name);
+ free(name);
+ return oname;
+ }
+
+ oprInfo = findOprByOid(atooid(opr));
+ if (oprInfo == NULL)
+ {
+ write_msg(NULL, "WARNING: could not find operator with OID %s\n",
+ opr);
+ return NULL;
+ }
+ return pg_strdup(oprInfo->dobj.name);
+}
+
+/*
+ * Convert a function OID obtained from pg_ts_parser or pg_ts_template
+ *
+ * It is sufficient to use REGPROC rather than REGPROCEDURE, since the
+ * argument lists of these functions are predetermined. Note that the
+ * caller should ensure we are in the proper schema, because the results
+ * are search path dependent!
+ */
+static const char *
+convertTSFunction(Archive *fout, Oid funcOid)
+{
+ char *result;
+ char query[128];
+ PGresult *res;
+
+ snprintf(query, sizeof(query),
+ "SELECT '%u'::pg_catalog.regproc", funcOid);
+ res = ExecuteSqlQueryForSingleRow(fout, query);
+
+ result = pg_strdup(PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+
+ return result;
+}
+
+
+/*
+ * dumpOpclass
+ * write out a single operator class definition
+ */
+static void
+dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PGresult *res;
+ int ntups;
+ int i_opcintype;
+ int i_opckeytype;
+ int i_opcdefault;
+ int i_opcfamily;
+ int i_opcfamilyname;
+ int i_opcfamilynsp;
+ int i_amname;
+ int i_amopstrategy;
+ int i_amopreqcheck;
+ int i_amopopr;
+ int i_sortfamily;
+ int i_sortfamilynsp;
+ int i_amprocnum;
+ int i_amproc;
+ int i_amproclefttype;
+ int i_amprocrighttype;
+ char *opcintype;
+ char *opckeytype;
+ char *opcdefault;
+ char *opcfamily;
+ char *opcfamilyname;
+ char *opcfamilynsp;
+ char *amname;
+ char *amopstrategy;
+ char *amopreqcheck;
+ char *amopopr;
+ char *sortfamily;
+ char *sortfamilynsp;
+ char *amprocnum;
+ char *amproc;
+ char *amproclefttype;
+ char *amprocrighttype;
+ bool needComma;
+ int i;
+
+ /* Skip if not to be dumped */
+ if (!opcinfo->dobj.dump || dataOnly)
+ return;
+
+ /*
+ * XXX currently we do not implement dumping of operator classes from
+ * pre-7.3 databases. This could be done but it seems not worth the
+ * trouble.
+ */
+ if (fout->remoteVersion < 70300)
+ return;
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema so regoperator works correctly */
+ selectSourceSchema(fout, opcinfo->dobj.namespace->dobj.name);
+
+ /* Get additional fields from the pg_opclass row */
+ if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
+ "opckeytype::pg_catalog.regtype, "
+ "opcdefault, opcfamily, "
+ "opfname AS opcfamilyname, "
+ "nspname AS opcfamilynsp, "
+ "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
+ "FROM pg_catalog.pg_opclass c "
+ "LEFT JOIN pg_catalog.pg_opfamily f ON f.oid = opcfamily "
+ "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = opfnamespace "
+ "WHERE c.oid = '%u'::pg_catalog.oid",
+ opcinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
+ "opckeytype::pg_catalog.regtype, "
+ "opcdefault, NULL AS opcfamily, "
+ "NULL AS opcfamilyname, "
+ "NULL AS opcfamilynsp, "
+ "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcamid) AS amname "
+ "FROM pg_catalog.pg_opclass "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ opcinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ i_opcintype = PQfnumber(res, "opcintype");
+ i_opckeytype = PQfnumber(res, "opckeytype");
+ i_opcdefault = PQfnumber(res, "opcdefault");
+ i_opcfamily = PQfnumber(res, "opcfamily");
+ i_opcfamilyname = PQfnumber(res, "opcfamilyname");
+ i_opcfamilynsp = PQfnumber(res, "opcfamilynsp");
+ i_amname = PQfnumber(res, "amname");
+
+ opcintype = PQgetvalue(res, 0, i_opcintype);
+ opckeytype = PQgetvalue(res, 0, i_opckeytype);
+ opcdefault = PQgetvalue(res, 0, i_opcdefault);
+ /* opcfamily will still be needed after we PQclear res */
+ opcfamily = pg_strdup(PQgetvalue(res, 0, i_opcfamily));
+ opcfamilyname = PQgetvalue(res, 0, i_opcfamilyname);
+ opcfamilynsp = PQgetvalue(res, 0, i_opcfamilynsp);
+ /* amname will still be needed after we PQclear res */
+ amname = pg_strdup(PQgetvalue(res, 0, i_amname));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP OPERATOR CLASS %s",
+ fmtId(opcinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s",
+ fmtId(opcinfo->dobj.name));
+ appendPQExpBuffer(delq, " USING %s;\n",
+ fmtId(amname));
+
+ /* Build the fixed portion of the CREATE command */
+ appendPQExpBuffer(q, "CREATE OPERATOR CLASS %s\n ",
+ fmtId(opcinfo->dobj.name));
+ if (strcmp(opcdefault, "t") == 0)
+ appendPQExpBufferStr(q, "DEFAULT ");
+ appendPQExpBuffer(q, "FOR TYPE %s USING %s",
+ opcintype,
+ fmtId(amname));
+ if (strlen(opcfamilyname) > 0 &&
+ (strcmp(opcfamilyname, opcinfo->dobj.name) != 0 ||
+ strcmp(opcfamilynsp, opcinfo->dobj.namespace->dobj.name) != 0))
+ {
+ appendPQExpBufferStr(q, " FAMILY ");
+ if (strcmp(opcfamilynsp, opcinfo->dobj.namespace->dobj.name) != 0)
+ appendPQExpBuffer(q, "%s.", fmtId(opcfamilynsp));
+ appendPQExpBuffer(q, "%s", fmtId(opcfamilyname));
+ }
+ appendPQExpBufferStr(q, " AS\n ");
+
+ needComma = false;
+
+ if (strcmp(opckeytype, "-") != 0)
+ {
+ appendPQExpBuffer(q, "STORAGE %s",
+ opckeytype);
+ needComma = true;
+ }
+
+ PQclear(res);
+
+ /*
+ * Now fetch and print the OPERATOR entries (pg_amop rows).
+ *
+ * Print only those opfamily members that are tied to the opclass by
+ * pg_depend entries.
+ *
+ * XXX RECHECK is gone as of 8.4, but we'll still print it if dumping an
+ * older server's opclass in which it is used. This is to avoid
+ * hard-to-detect breakage if a newer pg_dump is used to dump from an
+ * older server and then reload into that old version. This can go away
+ * once 8.3 is so old as to not be of interest to anyone.
+ */
+ resetPQExpBuffer(query);
+
+ if (fout->remoteVersion >= 90100)
+ {
+ appendPQExpBuffer(query, "SELECT amopstrategy, false AS amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "opfname AS sortfamily, "
+ "nspname AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop ao JOIN pg_catalog.pg_depend ON "
+ "(classid = 'pg_catalog.pg_amop'::pg_catalog.regclass AND objid = ao.oid) "
+ "LEFT JOIN pg_catalog.pg_opfamily f ON f.oid = amopsortfamily "
+ "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = opfnamespace "
+ "WHERE refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND amopfamily = '%s'::pg_catalog.oid "
+ "ORDER BY amopstrategy",
+ opcinfo->dobj.catId.oid,
+ opcfamily);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(query, "SELECT amopstrategy, false AS amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "NULL AS sortfamily, "
+ "NULL AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop ao, pg_catalog.pg_depend "
+ "WHERE refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND classid = 'pg_catalog.pg_amop'::pg_catalog.regclass "
+ "AND objid = ao.oid "
+ "ORDER BY amopstrategy",
+ opcinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBuffer(query, "SELECT amopstrategy, amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "NULL AS sortfamily, "
+ "NULL AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop ao, pg_catalog.pg_depend "
+ "WHERE refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND classid = 'pg_catalog.pg_amop'::pg_catalog.regclass "
+ "AND objid = ao.oid "
+ "ORDER BY amopstrategy",
+ opcinfo->dobj.catId.oid);
+ }
+ else
+ {
+ /*
+ * Here, we print all entries since there are no opfamilies and hence
+ * no loose operators to worry about.
+ */
+ appendPQExpBuffer(query, "SELECT amopstrategy, amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "NULL AS sortfamily, "
+ "NULL AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop "
+ "WHERE amopclaid = '%u'::pg_catalog.oid "
+ "ORDER BY amopstrategy",
+ opcinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_amopstrategy = PQfnumber(res, "amopstrategy");
+ i_amopreqcheck = PQfnumber(res, "amopreqcheck");
+ i_amopopr = PQfnumber(res, "amopopr");
+ i_sortfamily = PQfnumber(res, "sortfamily");
+ i_sortfamilynsp = PQfnumber(res, "sortfamilynsp");
+
+ for (i = 0; i < ntups; i++)
+ {
+ amopstrategy = PQgetvalue(res, i, i_amopstrategy);
+ amopreqcheck = PQgetvalue(res, i, i_amopreqcheck);
+ amopopr = PQgetvalue(res, i, i_amopopr);
+ sortfamily = PQgetvalue(res, i, i_sortfamily);
+ sortfamilynsp = PQgetvalue(res, i, i_sortfamilynsp);
+
+ if (needComma)
+ appendPQExpBufferStr(q, " ,\n ");
+
+ appendPQExpBuffer(q, "OPERATOR %s %s",
+ amopstrategy, amopopr);
+
+ if (strlen(sortfamily) > 0)
+ {
+ appendPQExpBufferStr(q, " FOR ORDER BY ");
+ if (strcmp(sortfamilynsp, opcinfo->dobj.namespace->dobj.name) != 0)
+ appendPQExpBuffer(q, "%s.", fmtId(sortfamilynsp));
+ appendPQExpBufferStr(q, fmtId(sortfamily));
+ }
+
+ if (strcmp(amopreqcheck, "t") == 0)
+ appendPQExpBufferStr(q, " RECHECK");
+
+ needComma = true;
+ }
+
+ PQclear(res);
+
+ /*
+ * Now fetch and print the FUNCTION entries (pg_amproc rows).
+ *
+ * Print only those opfamily members that are tied to the opclass by
+ * pg_depend entries.
+ *
+ * We print the amproclefttype/amprocrighttype even though in most cases
+ * the backend could deduce the right values, because of the corner case
+ * of a btree sort support function for a cross-type comparison. That's
+ * only allowed in 9.2 and later, but for simplicity print them in all
+ * versions that have the columns.
+ */
+ resetPQExpBuffer(query);
+
+ if (fout->remoteVersion >= 80300)
+ {
+ appendPQExpBuffer(query, "SELECT amprocnum, "
+ "amproc::pg_catalog.regprocedure, "
+ "amproclefttype::pg_catalog.regtype, "
+ "amprocrighttype::pg_catalog.regtype "
+ "FROM pg_catalog.pg_amproc ap, pg_catalog.pg_depend "
+ "WHERE refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass "
+ "AND objid = ap.oid "
+ "ORDER BY amprocnum",
+ opcinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT amprocnum, "
+ "amproc::pg_catalog.regprocedure, "
+ "'' AS amproclefttype, "
+ "'' AS amprocrighttype "
+ "FROM pg_catalog.pg_amproc "
+ "WHERE amopclaid = '%u'::pg_catalog.oid "
+ "ORDER BY amprocnum",
+ opcinfo->dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_amprocnum = PQfnumber(res, "amprocnum");
+ i_amproc = PQfnumber(res, "amproc");
+ i_amproclefttype = PQfnumber(res, "amproclefttype");
+ i_amprocrighttype = PQfnumber(res, "amprocrighttype");
+
+ for (i = 0; i < ntups; i++)
+ {
+ amprocnum = PQgetvalue(res, i, i_amprocnum);
+ amproc = PQgetvalue(res, i, i_amproc);
+ amproclefttype = PQgetvalue(res, i, i_amproclefttype);
+ amprocrighttype = PQgetvalue(res, i, i_amprocrighttype);
+
+ if (needComma)
+ appendPQExpBufferStr(q, " ,\n ");
+
+ appendPQExpBuffer(q, "FUNCTION %s", amprocnum);
+
+ if (*amproclefttype && *amprocrighttype)
+ appendPQExpBuffer(q, " (%s, %s)", amproclefttype, amprocrighttype);
+
+ appendPQExpBuffer(q, " %s", amproc);
+
+ needComma = true;
+ }
+
+ PQclear(res);
+
+ appendPQExpBufferStr(q, ";\n");
+
+ appendPQExpBuffer(labelq, "OPERATOR CLASS %s",
+ fmtId(opcinfo->dobj.name));
+ appendPQExpBuffer(labelq, " USING %s",
+ fmtId(amname));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &opcinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, opcinfo->dobj.catId, opcinfo->dobj.dumpId,
+ opcinfo->dobj.name,
+ opcinfo->dobj.namespace->dobj.name,
+ NULL,
+ opcinfo->rolname,
+ false, "OPERATOR CLASS", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Operator Class Comments */
+ dumpComment(fout, labelq->data,
+ NULL, opcinfo->rolname,
+ opcinfo->dobj.catId, 0, opcinfo->dobj.dumpId);
+
+ free(amname);
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpOpfamily
+ * write out a single operator family definition
+ *
+ * Note: this also dumps any "loose" operator members that aren't bound to a
+ * specific opclass within the opfamily.
+ */
+static void
+dumpOpfamily(Archive *fout, OpfamilyInfo *opfinfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PGresult *res;
+ PGresult *res_ops;
+ PGresult *res_procs;
+ int ntups;
+ int i_amname;
+ int i_amopstrategy;
+ int i_amopreqcheck;
+ int i_amopopr;
+ int i_sortfamily;
+ int i_sortfamilynsp;
+ int i_amprocnum;
+ int i_amproc;
+ int i_amproclefttype;
+ int i_amprocrighttype;
+ char *amname;
+ char *amopstrategy;
+ char *amopreqcheck;
+ char *amopopr;
+ char *sortfamily;
+ char *sortfamilynsp;
+ char *amprocnum;
+ char *amproc;
+ char *amproclefttype;
+ char *amprocrighttype;
+ bool needComma;
+ int i;
+
+ /* Skip if not to be dumped */
+ if (!opfinfo->dobj.dump || dataOnly)
+ return;
+
+ /*
+ * We want to dump the opfamily only if (1) it contains "loose" operators
+ * or functions, or (2) it contains an opclass with a different name or
+ * owner. Otherwise it's sufficient to let it be created during creation
+ * of the contained opclass, and not dumping it improves portability of
+ * the dump. Since we have to fetch the loose operators/funcs anyway, do
+ * that first.
+ */
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema so regoperator works correctly */
+ selectSourceSchema(fout, opfinfo->dobj.namespace->dobj.name);
+
+ /*
+ * Fetch only those opfamily members that are tied directly to the
+ * opfamily by pg_depend entries.
+ *
+ * XXX RECHECK is gone as of 8.4, but we'll still print it if dumping an
+ * older server's opclass in which it is used. This is to avoid
+ * hard-to-detect breakage if a newer pg_dump is used to dump from an
+ * older server and then reload into that old version. This can go away
+ * once 8.3 is so old as to not be of interest to anyone.
+ */
+ if (fout->remoteVersion >= 90100)
+ {
+ appendPQExpBuffer(query, "SELECT amopstrategy, false AS amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "opfname AS sortfamily, "
+ "nspname AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop ao JOIN pg_catalog.pg_depend ON "
+ "(classid = 'pg_catalog.pg_amop'::pg_catalog.regclass AND objid = ao.oid) "
+ "LEFT JOIN pg_catalog.pg_opfamily f ON f.oid = amopsortfamily "
+ "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = opfnamespace "
+ "WHERE refclassid = 'pg_catalog.pg_opfamily'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND amopfamily = '%u'::pg_catalog.oid "
+ "ORDER BY amopstrategy",
+ opfinfo->dobj.catId.oid,
+ opfinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(query, "SELECT amopstrategy, false AS amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "NULL AS sortfamily, "
+ "NULL AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop ao, pg_catalog.pg_depend "
+ "WHERE refclassid = 'pg_catalog.pg_opfamily'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND classid = 'pg_catalog.pg_amop'::pg_catalog.regclass "
+ "AND objid = ao.oid "
+ "ORDER BY amopstrategy",
+ opfinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT amopstrategy, amopreqcheck, "
+ "amopopr::pg_catalog.regoperator, "
+ "NULL AS sortfamily, "
+ "NULL AS sortfamilynsp "
+ "FROM pg_catalog.pg_amop ao, pg_catalog.pg_depend "
+ "WHERE refclassid = 'pg_catalog.pg_opfamily'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND classid = 'pg_catalog.pg_amop'::pg_catalog.regclass "
+ "AND objid = ao.oid "
+ "ORDER BY amopstrategy",
+ opfinfo->dobj.catId.oid);
+ }
+
+ res_ops = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ resetPQExpBuffer(query);
+
+ appendPQExpBuffer(query, "SELECT amprocnum, "
+ "amproc::pg_catalog.regprocedure, "
+ "amproclefttype::pg_catalog.regtype, "
+ "amprocrighttype::pg_catalog.regtype "
+ "FROM pg_catalog.pg_amproc ap, pg_catalog.pg_depend "
+ "WHERE refclassid = 'pg_catalog.pg_opfamily'::pg_catalog.regclass "
+ "AND refobjid = '%u'::pg_catalog.oid "
+ "AND classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass "
+ "AND objid = ap.oid "
+ "ORDER BY amprocnum",
+ opfinfo->dobj.catId.oid);
+
+ res_procs = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res_ops) == 0 && PQntuples(res_procs) == 0)
+ {
+ /* No loose members, so check contained opclasses */
+ resetPQExpBuffer(query);
+
+ appendPQExpBuffer(query, "SELECT 1 "
+ "FROM pg_catalog.pg_opclass c, pg_catalog.pg_opfamily f, pg_catalog.pg_depend "
+ "WHERE f.oid = '%u'::pg_catalog.oid "
+ "AND refclassid = 'pg_catalog.pg_opfamily'::pg_catalog.regclass "
+ "AND refobjid = f.oid "
+ "AND classid = 'pg_catalog.pg_opclass'::pg_catalog.regclass "
+ "AND objid = c.oid "
+ "AND (opcname != opfname OR opcnamespace != opfnamespace OR opcowner != opfowner) "
+ "LIMIT 1",
+ opfinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) == 0)
+ {
+ /* no need to dump it, so bail out */
+ PQclear(res);
+ PQclear(res_ops);
+ PQclear(res_procs);
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ return;
+ }
+
+ PQclear(res);
+ }
+
+ /* Get additional fields from the pg_opfamily row */
+ resetPQExpBuffer(query);
+
+ appendPQExpBuffer(query, "SELECT "
+ "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opfmethod) AS amname "
+ "FROM pg_catalog.pg_opfamily "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ opfinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ i_amname = PQfnumber(res, "amname");
+
+ /* amname will still be needed after we PQclear res */
+ amname = pg_strdup(PQgetvalue(res, 0, i_amname));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP OPERATOR FAMILY %s",
+ fmtId(opfinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s",
+ fmtId(opfinfo->dobj.name));
+ appendPQExpBuffer(delq, " USING %s;\n",
+ fmtId(amname));
+
+ /* Build the fixed portion of the CREATE command */
+ appendPQExpBuffer(q, "CREATE OPERATOR FAMILY %s",
+ fmtId(opfinfo->dobj.name));
+ appendPQExpBuffer(q, " USING %s;\n",
+ fmtId(amname));
+
+ PQclear(res);
+
+ /* Do we need an ALTER to add loose members? */
+ if (PQntuples(res_ops) > 0 || PQntuples(res_procs) > 0)
+ {
+ appendPQExpBuffer(q, "ALTER OPERATOR FAMILY %s",
+ fmtId(opfinfo->dobj.name));
+ appendPQExpBuffer(q, " USING %s ADD\n ",
+ fmtId(amname));
+
+ needComma = false;
+
+ /*
+ * Now fetch and print the OPERATOR entries (pg_amop rows).
+ */
+ ntups = PQntuples(res_ops);
+
+ i_amopstrategy = PQfnumber(res_ops, "amopstrategy");
+ i_amopreqcheck = PQfnumber(res_ops, "amopreqcheck");
+ i_amopopr = PQfnumber(res_ops, "amopopr");
+ i_sortfamily = PQfnumber(res_ops, "sortfamily");
+ i_sortfamilynsp = PQfnumber(res_ops, "sortfamilynsp");
+
+ for (i = 0; i < ntups; i++)
+ {
+ amopstrategy = PQgetvalue(res_ops, i, i_amopstrategy);
+ amopreqcheck = PQgetvalue(res_ops, i, i_amopreqcheck);
+ amopopr = PQgetvalue(res_ops, i, i_amopopr);
+ sortfamily = PQgetvalue(res_ops, i, i_sortfamily);
+ sortfamilynsp = PQgetvalue(res_ops, i, i_sortfamilynsp);
+
+ if (needComma)
+ appendPQExpBufferStr(q, " ,\n ");
+
+ appendPQExpBuffer(q, "OPERATOR %s %s",
+ amopstrategy, amopopr);
+
+ if (strlen(sortfamily) > 0)
+ {
+ appendPQExpBufferStr(q, " FOR ORDER BY ");
+ if (strcmp(sortfamilynsp, opfinfo->dobj.namespace->dobj.name) != 0)
+ appendPQExpBuffer(q, "%s.", fmtId(sortfamilynsp));
+ appendPQExpBufferStr(q, fmtId(sortfamily));
+ }
+
+ if (strcmp(amopreqcheck, "t") == 0)
+ appendPQExpBufferStr(q, " RECHECK");
+
+ needComma = true;
+ }
+
+ /*
+ * Now fetch and print the FUNCTION entries (pg_amproc rows).
+ */
+ ntups = PQntuples(res_procs);
+
+ i_amprocnum = PQfnumber(res_procs, "amprocnum");
+ i_amproc = PQfnumber(res_procs, "amproc");
+ i_amproclefttype = PQfnumber(res_procs, "amproclefttype");
+ i_amprocrighttype = PQfnumber(res_procs, "amprocrighttype");
+
+ for (i = 0; i < ntups; i++)
+ {
+ amprocnum = PQgetvalue(res_procs, i, i_amprocnum);
+ amproc = PQgetvalue(res_procs, i, i_amproc);
+ amproclefttype = PQgetvalue(res_procs, i, i_amproclefttype);
+ amprocrighttype = PQgetvalue(res_procs, i, i_amprocrighttype);
+
+ if (needComma)
+ appendPQExpBufferStr(q, " ,\n ");
+
+ appendPQExpBuffer(q, "FUNCTION %s (%s, %s) %s",
+ amprocnum, amproclefttype, amprocrighttype,
+ amproc);
+
+ needComma = true;
+ }
+
+ appendPQExpBufferStr(q, ";\n");
+ }
+
+ appendPQExpBuffer(labelq, "OPERATOR FAMILY %s",
+ fmtId(opfinfo->dobj.name));
+ appendPQExpBuffer(labelq, " USING %s",
+ fmtId(amname));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &opfinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, opfinfo->dobj.catId, opfinfo->dobj.dumpId,
+ opfinfo->dobj.name,
+ opfinfo->dobj.namespace->dobj.name,
+ NULL,
+ opfinfo->rolname,
+ false, "OPERATOR FAMILY", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Operator Family Comments */
+ dumpComment(fout, labelq->data,
+ NULL, opfinfo->rolname,
+ opfinfo->dobj.catId, 0, opfinfo->dobj.dumpId);
+
+ free(amname);
+ PQclear(res_ops);
+ PQclear(res_procs);
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpCollation
+ * write out a single collation definition
+ */
+static void
+dumpCollation(Archive *fout, CollInfo *collinfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PGresult *res;
+ int i_collcollate;
+ int i_collctype;
+ const char *collcollate;
+ const char *collctype;
+
+ /* Skip if not to be dumped */
+ if (!collinfo->dobj.dump || dataOnly)
+ return;
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, collinfo->dobj.namespace->dobj.name);
+
+ /* Get conversion-specific details */
+ appendPQExpBuffer(query, "SELECT "
+ "collcollate, "
+ "collctype "
+ "FROM pg_catalog.pg_collation c "
+ "WHERE c.oid = '%u'::pg_catalog.oid",
+ collinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ i_collcollate = PQfnumber(res, "collcollate");
+ i_collctype = PQfnumber(res, "collctype");
+
+ collcollate = PQgetvalue(res, 0, i_collcollate);
+ collctype = PQgetvalue(res, 0, i_collctype);
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP COLLATION %s",
+ fmtId(collinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s;\n",
+ fmtId(collinfo->dobj.name));
+
+ appendPQExpBuffer(q, "CREATE COLLATION %s (lc_collate = ",
+ fmtId(collinfo->dobj.name));
+ appendStringLiteralAH(q, collcollate, fout);
+ appendPQExpBufferStr(q, ", lc_ctype = ");
+ appendStringLiteralAH(q, collctype, fout);
+ appendPQExpBufferStr(q, ");\n");
+
+ appendPQExpBuffer(labelq, "COLLATION %s", fmtId(collinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &collinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, collinfo->dobj.catId, collinfo->dobj.dumpId,
+ collinfo->dobj.name,
+ collinfo->dobj.namespace->dobj.name,
+ NULL,
+ collinfo->rolname,
+ false, "COLLATION", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Collation Comments */
+ dumpComment(fout, labelq->data,
+ collinfo->dobj.namespace->dobj.name, collinfo->rolname,
+ collinfo->dobj.catId, 0, collinfo->dobj.dumpId);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpConversion
+ * write out a single conversion definition
+ */
+static void
+dumpConversion(Archive *fout, ConvInfo *convinfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PGresult *res;
+ int i_conforencoding;
+ int i_contoencoding;
+ int i_conproc;
+ int i_condefault;
+ const char *conforencoding;
+ const char *contoencoding;
+ const char *conproc;
+ bool condefault;
+
+ /* Skip if not to be dumped */
+ if (!convinfo->dobj.dump || dataOnly)
+ return;
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, convinfo->dobj.namespace->dobj.name);
+
+ /* Get conversion-specific details */
+ appendPQExpBuffer(query, "SELECT "
+ "pg_catalog.pg_encoding_to_char(conforencoding) AS conforencoding, "
+ "pg_catalog.pg_encoding_to_char(contoencoding) AS contoencoding, "
+ "conproc, condefault "
+ "FROM pg_catalog.pg_conversion c "
+ "WHERE c.oid = '%u'::pg_catalog.oid",
+ convinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ i_conforencoding = PQfnumber(res, "conforencoding");
+ i_contoencoding = PQfnumber(res, "contoencoding");
+ i_conproc = PQfnumber(res, "conproc");
+ i_condefault = PQfnumber(res, "condefault");
+
+ conforencoding = PQgetvalue(res, 0, i_conforencoding);
+ contoencoding = PQgetvalue(res, 0, i_contoencoding);
+ conproc = PQgetvalue(res, 0, i_conproc);
+ condefault = (PQgetvalue(res, 0, i_condefault)[0] == 't');
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP CONVERSION %s",
+ fmtId(convinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s;\n",
+ fmtId(convinfo->dobj.name));
+
+ appendPQExpBuffer(q, "CREATE %sCONVERSION %s FOR ",
+ (condefault) ? "DEFAULT " : "",
+ fmtId(convinfo->dobj.name));
+ appendStringLiteralAH(q, conforencoding, fout);
+ appendPQExpBufferStr(q, " TO ");
+ appendStringLiteralAH(q, contoencoding, fout);
+ /* regproc is automatically quoted in 7.3 and above */
+ appendPQExpBuffer(q, " FROM %s;\n", conproc);
+
+ appendPQExpBuffer(labelq, "CONVERSION %s", fmtId(convinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &convinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, convinfo->dobj.catId, convinfo->dobj.dumpId,
+ convinfo->dobj.name,
+ convinfo->dobj.namespace->dobj.name,
+ NULL,
+ convinfo->rolname,
+ false, "CONVERSION", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Conversion Comments */
+ dumpComment(fout, labelq->data,
+ convinfo->dobj.namespace->dobj.name, convinfo->rolname,
+ convinfo->dobj.catId, 0, convinfo->dobj.dumpId);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * format_aggregate_signature: generate aggregate name and argument list
+ *
+ * The argument type names are qualified if needed. The aggregate name
+ * is never qualified.
+ */
+static char *
+format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes)
+{
+ PQExpBufferData buf;
+ int j;
+
+ initPQExpBuffer(&buf);
+ if (honor_quotes)
+ appendPQExpBufferStr(&buf, fmtId(agginfo->aggfn.dobj.name));
+ else
+ appendPQExpBufferStr(&buf, agginfo->aggfn.dobj.name);
+
+ if (agginfo->aggfn.nargs == 0)
+ appendPQExpBuffer(&buf, "(*)");
+ else
+ {
+ appendPQExpBufferChar(&buf, '(');
+ for (j = 0; j < agginfo->aggfn.nargs; j++)
+ {
+ char *typname;
+
+ typname = getFormattedTypeName(fout, agginfo->aggfn.argtypes[j],
+ zeroAsOpaque);
+
+ appendPQExpBuffer(&buf, "%s%s",
+ (j > 0) ? ", " : "",
+ typname);
+ free(typname);
+ }
+ appendPQExpBufferChar(&buf, ')');
+ }
+ return buf.data;
+}
+
+/*
+ * dumpAgg
+ * write out a single aggregate definition
+ */
+static void
+dumpAgg(Archive *fout, AggInfo *agginfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PQExpBuffer details;
+ char *aggsig; /* identity signature */
+ char *aggfullsig = NULL; /* full signature */
+ char *aggsig_tag;
+ PGresult *res;
+ int i_aggtransfn;
+ int i_aggfinalfn;
+ int i_aggmtransfn;
+ int i_aggminvtransfn;
+ int i_aggmfinalfn;
+ int i_aggfinalextra;
+ int i_aggmfinalextra;
+ int i_aggsortop;
+ int i_hypothetical;
+ int i_aggtranstype;
+ int i_aggtransspace;
+ int i_aggmtranstype;
+ int i_aggmtransspace;
+ int i_agginitval;
+ int i_aggminitval;
+ int i_convertok;
+ const char *aggtransfn;
+ const char *aggfinalfn;
+ const char *aggmtransfn;
+ const char *aggminvtransfn;
+ const char *aggmfinalfn;
+ bool aggfinalextra;
+ bool aggmfinalextra;
+ const char *aggsortop;
+ char *aggsortconvop;
+ bool hypothetical;
+ const char *aggtranstype;
+ const char *aggtransspace;
+ const char *aggmtranstype;
+ const char *aggmtransspace;
+ const char *agginitval;
+ const char *aggminitval;
+ bool convertok;
+
+ /* Skip if not to be dumped */
+ if (!agginfo->aggfn.dobj.dump || dataOnly)
+ return;
+
+ query = createPQExpBuffer();
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+ details = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
+
+ /* Get aggregate-specific details */
+ if (fout->remoteVersion >= 90400)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "aggmtransfn, aggminvtransfn, aggmfinalfn, "
+ "aggmtranstype::pg_catalog.regtype, "
+ "aggfinalextra, aggmfinalextra, "
+ "aggsortop::pg_catalog.regoperator, "
+ "(aggkind = 'h') AS hypothetical, "
+ "aggtransspace, agginitval, "
+ "aggmtransspace, aggminitval, "
+ "true AS convertok, "
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "'-' AS aggmtransfn, '-' AS aggminvtransfn, "
+ "'-' AS aggmfinalfn, 0 AS aggmtranstype, "
+ "false AS aggfinalextra, false AS aggmfinalextra, "
+ "aggsortop::pg_catalog.regoperator, "
+ "false AS hypothetical, "
+ "0 AS aggtransspace, agginitval, "
+ "0 AS aggmtransspace, NULL AS aggminitval, "
+ "true AS convertok, "
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 80100)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "'-' AS aggmtransfn, '-' AS aggminvtransfn, "
+ "'-' AS aggmfinalfn, 0 AS aggmtranstype, "
+ "false AS aggfinalextra, false AS aggmfinalextra, "
+ "aggsortop::pg_catalog.regoperator, "
+ "false AS hypothetical, "
+ "0 AS aggtransspace, agginitval, "
+ "0 AS aggmtransspace, NULL AS aggminitval, "
+ "true AS convertok "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "'-' AS aggmtransfn, '-' AS aggminvtransfn, "
+ "'-' AS aggmfinalfn, 0 AS aggmtranstype, "
+ "false AS aggfinalextra, false AS aggmfinalextra, "
+ "0 AS aggsortop, "
+ "false AS hypothetical, "
+ "0 AS aggtransspace, agginitval, "
+ "0 AS aggmtransspace, NULL AS aggminitval, "
+ "true AS convertok "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, "
+ "format_type(aggtranstype, NULL) AS aggtranstype, "
+ "'-' AS aggmtransfn, '-' AS aggminvtransfn, "
+ "'-' AS aggmfinalfn, 0 AS aggmtranstype, "
+ "false AS aggfinalextra, false AS aggmfinalextra, "
+ "0 AS aggsortop, "
+ "false AS hypothetical, "
+ "0 AS aggtransspace, agginitval, "
+ "0 AS aggmtransspace, NULL AS aggminitval, "
+ "true AS convertok "
+ "FROM pg_aggregate "
+ "WHERE oid = '%u'::oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn1 AS aggtransfn, "
+ "aggfinalfn, "
+ "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, "
+ "'-' AS aggmtransfn, '-' AS aggminvtransfn, "
+ "'-' AS aggmfinalfn, 0 AS aggmtranstype, "
+ "false AS aggfinalextra, false AS aggmfinalextra, "
+ "0 AS aggsortop, "
+ "false AS hypothetical, "
+ "0 AS aggtransspace, agginitval1 AS agginitval, "
+ "0 AS aggmtransspace, NULL AS aggminitval, "
+ "(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok "
+ "FROM pg_aggregate "
+ "WHERE oid = '%u'::oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ i_aggtransfn = PQfnumber(res, "aggtransfn");
+ i_aggfinalfn = PQfnumber(res, "aggfinalfn");
+ i_aggmtransfn = PQfnumber(res, "aggmtransfn");
+ i_aggminvtransfn = PQfnumber(res, "aggminvtransfn");
+ i_aggmfinalfn = PQfnumber(res, "aggmfinalfn");
+ i_aggfinalextra = PQfnumber(res, "aggfinalextra");
+ i_aggmfinalextra = PQfnumber(res, "aggmfinalextra");
+ i_aggsortop = PQfnumber(res, "aggsortop");
+ i_hypothetical = PQfnumber(res, "hypothetical");
+ i_aggtranstype = PQfnumber(res, "aggtranstype");
+ i_aggtransspace = PQfnumber(res, "aggtransspace");
+ i_aggmtranstype = PQfnumber(res, "aggmtranstype");
+ i_aggmtransspace = PQfnumber(res, "aggmtransspace");
+ i_agginitval = PQfnumber(res, "agginitval");
+ i_aggminitval = PQfnumber(res, "aggminitval");
+ i_convertok = PQfnumber(res, "convertok");
+
+ aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
+ aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
+ aggmtransfn = PQgetvalue(res, 0, i_aggmtransfn);
+ aggminvtransfn = PQgetvalue(res, 0, i_aggminvtransfn);
+ aggmfinalfn = PQgetvalue(res, 0, i_aggmfinalfn);
+ aggfinalextra = (PQgetvalue(res, 0, i_aggfinalextra)[0] == 't');
+ aggmfinalextra = (PQgetvalue(res, 0, i_aggmfinalextra)[0] == 't');
+ aggsortop = PQgetvalue(res, 0, i_aggsortop);
+ hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't');
+ aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
+ aggtransspace = PQgetvalue(res, 0, i_aggtransspace);
+ aggmtranstype = PQgetvalue(res, 0, i_aggmtranstype);
+ aggmtransspace = PQgetvalue(res, 0, i_aggmtransspace);
+ agginitval = PQgetvalue(res, 0, i_agginitval);
+ aggminitval = PQgetvalue(res, 0, i_aggminitval);
+ convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
+
+ if (fout->remoteVersion >= 80400)
+ {
+ /* 8.4 or later; we rely on server-side code for most of the work */
+ char *funcargs;
+ char *funciargs;
+
+ funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
+ funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs"));
+ aggfullsig = format_function_arguments(&agginfo->aggfn, funcargs, true);
+ aggsig = format_function_arguments(&agginfo->aggfn, funciargs, true);
+ }
+ else
+ /* pre-8.4, do it ourselves */
+ aggsig = format_aggregate_signature(agginfo, fout, true);
+
+ aggsig_tag = format_aggregate_signature(agginfo, fout, false);
+
+ if (!convertok)
+ {
+ write_msg(NULL, "WARNING: aggregate function %s could not be dumped correctly for this database version; ignored\n",
+ aggsig);
+
+ if (aggfullsig)
+ free(aggfullsig);
+
+ free(aggsig);
+
+ return;
+ }
+
+ if (fout->remoteVersion >= 70300)
+ {
+ /* If using 7.3's regproc or regtype, data is already quoted */
+ appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
+ aggtransfn,
+ aggtranstype);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ /* format_type quotes, regproc does not */
+ appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
+ fmtId(aggtransfn),
+ aggtranstype);
+ }
+ else
+ {
+ /* need quotes all around */
+ appendPQExpBuffer(details, " SFUNC = %s,\n",
+ fmtId(aggtransfn));
+ appendPQExpBuffer(details, " STYPE = %s",
+ fmtId(aggtranstype));
+ }
+
+ if (strcmp(aggtransspace, "0") != 0)
+ {
+ appendPQExpBuffer(details, ",\n SSPACE = %s",
+ aggtransspace);
+ }
+
+ if (!PQgetisnull(res, 0, i_agginitval))
+ {
+ appendPQExpBufferStr(details, ",\n INITCOND = ");
+ appendStringLiteralAH(details, agginitval, fout);
+ }
+
+ if (strcmp(aggfinalfn, "-") != 0)
+ {
+ appendPQExpBuffer(details, ",\n FINALFUNC = %s",
+ aggfinalfn);
+ if (aggfinalextra)
+ appendPQExpBufferStr(details, ",\n FINALFUNC_EXTRA");
+ }
+
+ if (strcmp(aggmtransfn, "-") != 0)
+ {
+ appendPQExpBuffer(details, ",\n MSFUNC = %s,\n MINVFUNC = %s,\n MSTYPE = %s",
+ aggmtransfn,
+ aggminvtransfn,
+ aggmtranstype);
+ }
+
+ if (strcmp(aggmtransspace, "0") != 0)
+ {
+ appendPQExpBuffer(details, ",\n MSSPACE = %s",
+ aggmtransspace);
+ }
+
+ if (!PQgetisnull(res, 0, i_aggminitval))
+ {
+ appendPQExpBufferStr(details, ",\n MINITCOND = ");
+ appendStringLiteralAH(details, aggminitval, fout);
+ }
+
+ if (strcmp(aggmfinalfn, "-") != 0)
+ {
+ appendPQExpBuffer(details, ",\n MFINALFUNC = %s",
+ aggmfinalfn);
+ if (aggmfinalextra)
+ appendPQExpBufferStr(details, ",\n MFINALFUNC_EXTRA");
+ }
+
+ aggsortconvop = convertOperatorReference(fout, aggsortop);
+ if (aggsortconvop)
+ {
+ appendPQExpBuffer(details, ",\n SORTOP = %s",
+ aggsortconvop);
+ free(aggsortconvop);
+ }
+
+ if (hypothetical)
+ appendPQExpBufferStr(details, ",\n HYPOTHETICAL");
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP AGGREGATE %s.%s;\n",
+ fmtId(agginfo->aggfn.dobj.namespace->dobj.name),
+ aggsig);
+
+ appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
+ aggfullsig ? aggfullsig : aggsig, details->data);
+
+ appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &agginfo->aggfn.dobj, labelq->data);
+
+ ArchiveEntry(fout, agginfo->aggfn.dobj.catId, agginfo->aggfn.dobj.dumpId,
+ aggsig_tag,
+ agginfo->aggfn.dobj.namespace->dobj.name,
+ NULL,
+ agginfo->aggfn.rolname,
+ false, "AGGREGATE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Aggregate Comments */
+ dumpComment(fout, labelq->data,
+ agginfo->aggfn.dobj.namespace->dobj.name, agginfo->aggfn.rolname,
+ agginfo->aggfn.dobj.catId, 0, agginfo->aggfn.dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ agginfo->aggfn.dobj.namespace->dobj.name, agginfo->aggfn.rolname,
+ agginfo->aggfn.dobj.catId, 0, agginfo->aggfn.dobj.dumpId);
+
+ /*
+ * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL
+ * command look like a function's GRANT; in particular this affects the
+ * syntax for zero-argument aggregates and ordered-set aggregates.
+ */
+ free(aggsig);
+ free(aggsig_tag);
+
+ aggsig = format_function_signature(fout, &agginfo->aggfn, true);
+ aggsig_tag = format_function_signature(fout, &agginfo->aggfn, false);
+
+ dumpACL(fout, agginfo->aggfn.dobj.catId, agginfo->aggfn.dobj.dumpId,
+ "FUNCTION",
+ aggsig, NULL, aggsig_tag,
+ agginfo->aggfn.dobj.namespace->dobj.name,
+ agginfo->aggfn.rolname, agginfo->aggfn.proacl);
+
+ free(aggsig);
+ if (aggfullsig)
+ free(aggfullsig);
+ free(aggsig_tag);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(details);
+}
+
+/*
+ * dumpTSParser
+ * write out a single text search parser
+ */
+static void
+dumpTSParser(Archive *fout, TSParserInfo *prsinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+
+ /* Skip if not to be dumped */
+ if (!prsinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, prsinfo->dobj.namespace->dobj.name);
+
+ appendPQExpBuffer(q, "CREATE TEXT SEARCH PARSER %s (\n",
+ fmtId(prsinfo->dobj.name));
+
+ appendPQExpBuffer(q, " START = %s,\n",
+ convertTSFunction(fout, prsinfo->prsstart));
+ appendPQExpBuffer(q, " GETTOKEN = %s,\n",
+ convertTSFunction(fout, prsinfo->prstoken));
+ appendPQExpBuffer(q, " END = %s,\n",
+ convertTSFunction(fout, prsinfo->prsend));
+ if (prsinfo->prsheadline != InvalidOid)
+ appendPQExpBuffer(q, " HEADLINE = %s,\n",
+ convertTSFunction(fout, prsinfo->prsheadline));
+ appendPQExpBuffer(q, " LEXTYPES = %s );\n",
+ convertTSFunction(fout, prsinfo->prslextype));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP TEXT SEARCH PARSER %s",
+ fmtId(prsinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s;\n",
+ fmtId(prsinfo->dobj.name));
+
+ appendPQExpBuffer(labelq, "TEXT SEARCH PARSER %s",
+ fmtId(prsinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &prsinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, prsinfo->dobj.catId, prsinfo->dobj.dumpId,
+ prsinfo->dobj.name,
+ prsinfo->dobj.namespace->dobj.name,
+ NULL,
+ "",
+ false, "TEXT SEARCH PARSER", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Parser Comments */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ prsinfo->dobj.catId, 0, prsinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpTSDictionary
+ * write out a single text search dictionary
+ */
+static void
+dumpTSDictionary(Archive *fout, TSDictInfo *dictinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PQExpBuffer query;
+ PGresult *res;
+ char *nspname;
+ char *tmplname;
+
+ /* Skip if not to be dumped */
+ if (!dictinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+ query = createPQExpBuffer();
+
+ /* Fetch name and namespace of the dictionary's template */
+ selectSourceSchema(fout, "pg_catalog");
+ appendPQExpBuffer(query, "SELECT nspname, tmplname "
+ "FROM pg_ts_template p, pg_namespace n "
+ "WHERE p.oid = '%u' AND n.oid = tmplnamespace",
+ dictinfo->dicttemplate);
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+ nspname = PQgetvalue(res, 0, 0);
+ tmplname = PQgetvalue(res, 0, 1);
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, dictinfo->dobj.namespace->dobj.name);
+
+ appendPQExpBuffer(q, "CREATE TEXT SEARCH DICTIONARY %s (\n",
+ fmtId(dictinfo->dobj.name));
+
+ appendPQExpBufferStr(q, " TEMPLATE = ");
+ if (strcmp(nspname, dictinfo->dobj.namespace->dobj.name) != 0)
+ appendPQExpBuffer(q, "%s.", fmtId(nspname));
+ appendPQExpBufferStr(q, fmtId(tmplname));
+
+ PQclear(res);
+
+ /* the dictinitoption can be dumped straight into the command */
+ if (dictinfo->dictinitoption)
+ appendPQExpBuffer(q, ",\n %s", dictinfo->dictinitoption);
+
+ appendPQExpBufferStr(q, " );\n");
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP TEXT SEARCH DICTIONARY %s",
+ fmtId(dictinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s;\n",
+ fmtId(dictinfo->dobj.name));
+
+ appendPQExpBuffer(labelq, "TEXT SEARCH DICTIONARY %s",
+ fmtId(dictinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &dictinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, dictinfo->dobj.catId, dictinfo->dobj.dumpId,
+ dictinfo->dobj.name,
+ dictinfo->dobj.namespace->dobj.name,
+ NULL,
+ dictinfo->rolname,
+ false, "TEXT SEARCH DICTIONARY", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Dictionary Comments */
+ dumpComment(fout, labelq->data,
+ NULL, dictinfo->rolname,
+ dictinfo->dobj.catId, 0, dictinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpTSTemplate
+ * write out a single text search template
+ */
+static void
+dumpTSTemplate(Archive *fout, TSTemplateInfo *tmplinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+
+ /* Skip if not to be dumped */
+ if (!tmplinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, tmplinfo->dobj.namespace->dobj.name);
+
+ appendPQExpBuffer(q, "CREATE TEXT SEARCH TEMPLATE %s (\n",
+ fmtId(tmplinfo->dobj.name));
+
+ if (tmplinfo->tmplinit != InvalidOid)
+ appendPQExpBuffer(q, " INIT = %s,\n",
+ convertTSFunction(fout, tmplinfo->tmplinit));
+ appendPQExpBuffer(q, " LEXIZE = %s );\n",
+ convertTSFunction(fout, tmplinfo->tmpllexize));
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP TEXT SEARCH TEMPLATE %s",
+ fmtId(tmplinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s;\n",
+ fmtId(tmplinfo->dobj.name));
+
+ appendPQExpBuffer(labelq, "TEXT SEARCH TEMPLATE %s",
+ fmtId(tmplinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tmplinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tmplinfo->dobj.catId, tmplinfo->dobj.dumpId,
+ tmplinfo->dobj.name,
+ tmplinfo->dobj.namespace->dobj.name,
+ NULL,
+ "",
+ false, "TEXT SEARCH TEMPLATE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Template Comments */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ tmplinfo->dobj.catId, 0, tmplinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpTSConfig
+ * write out a single text search configuration
+ */
+static void
+dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PQExpBuffer query;
+ PGresult *res;
+ char *nspname;
+ char *prsname;
+ int ntups,
+ i;
+ int i_tokenname;
+ int i_dictname;
+
+ /* Skip if not to be dumped */
+ if (!cfginfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+ query = createPQExpBuffer();
+
+ /* Fetch name and namespace of the config's parser */
+ selectSourceSchema(fout, "pg_catalog");
+ appendPQExpBuffer(query, "SELECT nspname, prsname "
+ "FROM pg_ts_parser p, pg_namespace n "
+ "WHERE p.oid = '%u' AND n.oid = prsnamespace",
+ cfginfo->cfgparser);
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+ nspname = PQgetvalue(res, 0, 0);
+ prsname = PQgetvalue(res, 0, 1);
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, cfginfo->dobj.namespace->dobj.name);
+
+ appendPQExpBuffer(q, "CREATE TEXT SEARCH CONFIGURATION %s (\n",
+ fmtId(cfginfo->dobj.name));
+
+ appendPQExpBufferStr(q, " PARSER = ");
+ if (strcmp(nspname, cfginfo->dobj.namespace->dobj.name) != 0)
+ appendPQExpBuffer(q, "%s.", fmtId(nspname));
+ appendPQExpBuffer(q, "%s );\n", fmtId(prsname));
+
+ PQclear(res);
+
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query,
+ "SELECT \n"
+ " ( SELECT alias FROM pg_catalog.ts_token_type('%u'::pg_catalog.oid) AS t \n"
+ " WHERE t.tokid = m.maptokentype ) AS tokenname, \n"
+ " m.mapdict::pg_catalog.regdictionary AS dictname \n"
+ "FROM pg_catalog.pg_ts_config_map AS m \n"
+ "WHERE m.mapcfg = '%u' \n"
+ "ORDER BY m.mapcfg, m.maptokentype, m.mapseqno",
+ cfginfo->cfgparser, cfginfo->dobj.catId.oid);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+
+ i_tokenname = PQfnumber(res, "tokenname");
+ i_dictname = PQfnumber(res, "dictname");
+
+ for (i = 0; i < ntups; i++)
+ {
+ char *tokenname = PQgetvalue(res, i, i_tokenname);
+ char *dictname = PQgetvalue(res, i, i_dictname);
+
+ if (i == 0 ||
+ strcmp(tokenname, PQgetvalue(res, i - 1, i_tokenname)) != 0)
+ {
+ /* starting a new token type, so start a new command */
+ if (i > 0)
+ appendPQExpBufferStr(q, ";\n");
+ appendPQExpBuffer(q, "\nALTER TEXT SEARCH CONFIGURATION %s\n",
+ fmtId(cfginfo->dobj.name));
+ /* tokenname needs quoting, dictname does NOT */
+ appendPQExpBuffer(q, " ADD MAPPING FOR %s WITH %s",
+ fmtId(tokenname), dictname);
+ }
+ else
+ appendPQExpBuffer(q, ", %s", dictname);
+ }
+
+ if (ntups > 0)
+ appendPQExpBufferStr(q, ";\n");
+
+ PQclear(res);
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP TEXT SEARCH CONFIGURATION %s",
+ fmtId(cfginfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, ".%s;\n",
+ fmtId(cfginfo->dobj.name));
+
+ appendPQExpBuffer(labelq, "TEXT SEARCH CONFIGURATION %s",
+ fmtId(cfginfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &cfginfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, cfginfo->dobj.catId, cfginfo->dobj.dumpId,
+ cfginfo->dobj.name,
+ cfginfo->dobj.namespace->dobj.name,
+ NULL,
+ cfginfo->rolname,
+ false, "TEXT SEARCH CONFIGURATION", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Configuration Comments */
+ dumpComment(fout, labelq->data,
+ NULL, cfginfo->rolname,
+ cfginfo->dobj.catId, 0, cfginfo->dobj.dumpId);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpForeignDataWrapper
+ * write out a single foreign-data wrapper definition
+ */
+static void
+dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ char *qfdwname;
+
+ /* Skip if not to be dumped */
+ if (!fdwinfo->dobj.dump || dataOnly)
+ return;
+
+ /*
+ * FDWs that belong to an extension are dumped based on their "dump"
+ * field. Otherwise omit them if we are only dumping some specific object.
+ */
+ if (!fdwinfo->dobj.ext_member)
+ if (!include_everything)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ qfdwname = pg_strdup(fmtId(fdwinfo->dobj.name));
+
+ appendPQExpBuffer(q, "CREATE FOREIGN DATA WRAPPER %s",
+ qfdwname);
+
+ if (strcmp(fdwinfo->fdwhandler, "-") != 0)
+ appendPQExpBuffer(q, " HANDLER %s", fdwinfo->fdwhandler);
+
+ if (strcmp(fdwinfo->fdwvalidator, "-") != 0)
+ appendPQExpBuffer(q, " VALIDATOR %s", fdwinfo->fdwvalidator);
+
+ if (strlen(fdwinfo->fdwoptions) > 0)
+ appendPQExpBuffer(q, " OPTIONS (\n %s\n)", fdwinfo->fdwoptions);
+
+ appendPQExpBufferStr(q, ";\n");
+
+ appendPQExpBuffer(delq, "DROP FOREIGN DATA WRAPPER %s;\n",
+ qfdwname);
+
+ appendPQExpBuffer(labelq, "FOREIGN DATA WRAPPER %s",
+ qfdwname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &fdwinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, fdwinfo->dobj.catId, fdwinfo->dobj.dumpId,
+ fdwinfo->dobj.name,
+ NULL,
+ NULL,
+ fdwinfo->rolname,
+ false, "FOREIGN DATA WRAPPER", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Handle the ACL */
+ dumpACL(fout, fdwinfo->dobj.catId, fdwinfo->dobj.dumpId,
+ "FOREIGN DATA WRAPPER",
+ qfdwname, NULL, fdwinfo->dobj.name,
+ NULL, fdwinfo->rolname,
+ fdwinfo->fdwacl);
+
+ /* Dump Foreign Data Wrapper Comments */
+ dumpComment(fout, labelq->data,
+ NULL, fdwinfo->rolname,
+ fdwinfo->dobj.catId, 0, fdwinfo->dobj.dumpId);
+
+ free(qfdwname);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpForeignServer
+ * write out a foreign server definition
+ */
+static void
+dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ PQExpBuffer query;
+ PGresult *res;
+ char *qsrvname;
+ char *fdwname;
+
+ /* Skip if not to be dumped */
+ if (!srvinfo->dobj.dump || dataOnly || !include_everything)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+ query = createPQExpBuffer();
+
+ qsrvname = pg_strdup(fmtId(srvinfo->dobj.name));
+
+ /* look up the foreign-data wrapper */
+ selectSourceSchema(fout, "pg_catalog");
+ appendPQExpBuffer(query, "SELECT fdwname "
+ "FROM pg_foreign_data_wrapper w "
+ "WHERE w.oid = '%u'",
+ srvinfo->srvfdw);
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+ fdwname = PQgetvalue(res, 0, 0);
+
+ appendPQExpBuffer(q, "CREATE SERVER %s", qsrvname);
+ if (srvinfo->srvtype && strlen(srvinfo->srvtype) > 0)
+ {
+ appendPQExpBufferStr(q, " TYPE ");
+ appendStringLiteralAH(q, srvinfo->srvtype, fout);
+ }
+ if (srvinfo->srvversion && strlen(srvinfo->srvversion) > 0)
+ {
+ appendPQExpBufferStr(q, " VERSION ");
+ appendStringLiteralAH(q, srvinfo->srvversion, fout);
+ }
+
+ appendPQExpBufferStr(q, " FOREIGN DATA WRAPPER ");
+ appendPQExpBufferStr(q, fmtId(fdwname));
+
+ if (srvinfo->srvoptions && strlen(srvinfo->srvoptions) > 0)
+ appendPQExpBuffer(q, " OPTIONS (\n %s\n)", srvinfo->srvoptions);
+
+ appendPQExpBufferStr(q, ";\n");
+
+ appendPQExpBuffer(delq, "DROP SERVER %s;\n",
+ qsrvname);
+
+ appendPQExpBuffer(labelq, "SERVER %s", qsrvname);
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &srvinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, srvinfo->dobj.catId, srvinfo->dobj.dumpId,
+ srvinfo->dobj.name,
+ NULL,
+ NULL,
+ srvinfo->rolname,
+ false, "SERVER", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Handle the ACL */
+ dumpACL(fout, srvinfo->dobj.catId, srvinfo->dobj.dumpId,
+ "FOREIGN SERVER",
+ qsrvname, NULL, srvinfo->dobj.name,
+ NULL, srvinfo->rolname,
+ srvinfo->srvacl);
+
+ /* Dump user mappings */
+ dumpUserMappings(fout,
+ srvinfo->dobj.name, NULL,
+ srvinfo->rolname,
+ srvinfo->dobj.catId, srvinfo->dobj.dumpId);
+
+ /* Dump Foreign Server Comments */
+ dumpComment(fout, labelq->data,
+ NULL, srvinfo->rolname,
+ srvinfo->dobj.catId, 0, srvinfo->dobj.dumpId);
+
+ free(qsrvname);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpUserMappings
+ *
+ * This routine is used to dump any user mappings associated with the
+ * server handed to this routine. Should be called after ArchiveEntry()
+ * for the server.
+ */
+static void
+dumpUserMappings(Archive *fout,
+ const char *servername, const char *namespace,
+ const char *owner,
+ CatalogId catalogId, DumpId dumpId)
+{
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer query;
+ PQExpBuffer tag;
+ PGresult *res;
+ int ntups;
+ int i_usename;
+ int i_umoptions;
+ int i;
+
+ q = createPQExpBuffer();
+ tag = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ query = createPQExpBuffer();
+
+ /*
+ * We read from the publicly accessible view pg_user_mappings, so as not
+ * to fail if run by a non-superuser. Note that the view will show
+ * umoptions as null if the user hasn't got privileges for the associated
+ * server; this means that pg_dump will dump such a mapping, but with no
+ * OPTIONS clause. A possible alternative is to skip such mappings
+ * altogether, but it's not clear that that's an improvement.
+ */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query,
+ "SELECT usename, "
+ "array_to_string(ARRAY("
+ "SELECT quote_ident(option_name) || ' ' || "
+ "quote_literal(option_value) "
+ "FROM pg_options_to_table(umoptions) "
+ "ORDER BY option_name"
+ "), E',\n ') AS umoptions "
+ "FROM pg_user_mappings "
+ "WHERE srvid = '%u' "
+ "ORDER BY usename",
+ catalogId.oid);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+ i_usename = PQfnumber(res, "usename");
+ i_umoptions = PQfnumber(res, "umoptions");
+
+ for (i = 0; i < ntups; i++)
+ {
+ char *usename;
+ char *umoptions;
+
+ usename = PQgetvalue(res, i, i_usename);
+ umoptions = PQgetvalue(res, i, i_umoptions);
+
+ resetPQExpBuffer(q);
+ appendPQExpBuffer(q, "CREATE USER MAPPING FOR %s", fmtId(usename));
+ appendPQExpBuffer(q, " SERVER %s", fmtId(servername));
+
+ if (umoptions && strlen(umoptions) > 0)
+ appendPQExpBuffer(q, " OPTIONS (\n %s\n)", umoptions);
+
+ appendPQExpBufferStr(q, ";\n");
+
+ resetPQExpBuffer(delq);
+ appendPQExpBuffer(delq, "DROP USER MAPPING FOR %s", fmtId(usename));
+ appendPQExpBuffer(delq, " SERVER %s;\n", fmtId(servername));
+
+ resetPQExpBuffer(tag);
+ appendPQExpBuffer(tag, "USER MAPPING %s SERVER %s",
+ usename, servername);
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ tag->data,
+ namespace,
+ NULL,
+ owner, false,
+ "USER MAPPING", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ &dumpId, 1,
+ NULL, NULL);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(tag);
+ destroyPQExpBuffer(q);
+}
+
+/*
+ * Write out default privileges information
+ */
+static void
+dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo)
+{
+ PQExpBuffer q;
+ PQExpBuffer tag;
+ const char *type;
+
+ /* Skip if not to be dumped */
+ if (!daclinfo->dobj.dump || dataOnly || aclsSkip)
+ return;
+
+ q = createPQExpBuffer();
+ tag = createPQExpBuffer();
+
+ switch (daclinfo->defaclobjtype)
+ {
+ case DEFACLOBJ_RELATION:
+ type = "TABLES";
+ break;
+ case DEFACLOBJ_SEQUENCE:
+ type = "SEQUENCES";
+ break;
+ case DEFACLOBJ_FUNCTION:
+ type = "FUNCTIONS";
+ break;
+ case DEFACLOBJ_TYPE:
+ type = "TYPES";
+ break;
+ default:
+ /* shouldn't get here */
+ exit_horribly(NULL,
+ "unrecognized object type in default privileges: %d\n",
+ (int) daclinfo->defaclobjtype);
+ type = ""; /* keep compiler quiet */
+ }
+
+ appendPQExpBuffer(tag, "DEFAULT PRIVILEGES FOR %s", type);
+
+ /* build the actual command(s) for this tuple */
+ if (!buildDefaultACLCommands(type,
+ daclinfo->dobj.namespace != NULL ?
+ daclinfo->dobj.namespace->dobj.name : NULL,
+ daclinfo->defaclacl,
+ daclinfo->defaclrole,
+ fout->remoteVersion,
+ q))
+ exit_horribly(NULL, "could not parse default ACL list (%s)\n",
+ daclinfo->defaclacl);
+
+ ArchiveEntry(fout, daclinfo->dobj.catId, daclinfo->dobj.dumpId,
+ tag->data,
+ daclinfo->dobj.namespace ? daclinfo->dobj.namespace->dobj.name : NULL,
+ NULL,
+ daclinfo->defaclrole,
+ false, "DEFAULT ACL", SECTION_POST_DATA,
+ q->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ destroyPQExpBuffer(tag);
+ destroyPQExpBuffer(q);
+}
+
+/*----------
+ * Write out grant/revoke information
+ *
+ * 'objCatId' is the catalog ID of the underlying object.
+ * 'objDumpId' is the dump ID of the underlying object.
+ * 'type' must be one of
+ * TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
+ * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT.
+ * 'name' is the formatted name of the object. Must be quoted etc. already.
+ * 'subname' is the formatted name of the sub-object, if any. Must be quoted.
+ * 'tag' is the tag for the archive entry (typ. unquoted name of object).
+ * 'nspname' is the namespace the object is in (NULL if none).
+ * 'owner' is the owner, NULL if there is no owner (for languages).
+ * 'acls' is the string read out of the fooacl system catalog field;
+ * it will be parsed here.
+ *----------
+ */
+static void
+dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
+ const char *type, const char *name, const char *subname,
+ const char *tag, const char *nspname, const char *owner,
+ const char *acls)
+{
+ PQExpBuffer sql;
+
+ /* Do nothing if ACL dump is not enabled */
+ if (aclsSkip)
+ return;
+
+ /* --data-only skips ACLs *except* BLOB ACLs */
+ if (dataOnly && strcmp(type, "LARGE OBJECT") != 0)
+ return;
+
+ sql = createPQExpBuffer();
+
+ if (!buildACLCommands(name, subname, type, acls, owner,
+ "", fout->remoteVersion, sql))
+ exit_horribly(NULL,
+ "could not parse ACL list (%s) for object \"%s\" (%s)\n",
+ acls, name, type);
+
+ if (sql->len > 0)
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ tag, nspname,
+ NULL,
+ owner ? owner : "",
+ false, "ACL", SECTION_NONE,
+ sql->data, "", NULL,
+ &(objDumpId), 1,
+ NULL, NULL);
+
+ destroyPQExpBuffer(sql);
+}
+
+/*
+ * dumpSecLabel
+ *
+ * This routine is used to dump any security labels associated with the
+ * object handed to this routine. The routine takes a constant character
+ * string for the target part of the security-label command, plus
+ * the namespace and owner of the object (for labeling the ArchiveEntry),
+ * plus catalog ID and subid which are the lookup key for pg_seclabel,
+ * plus the dump ID for the object (for setting a dependency).
+ * If a matching pg_seclabel entry is found, it is dumped.
+ *
+ * Note: although this routine takes a dumpId for dependency purposes,
+ * that purpose is just to mark the dependency in the emitted dump file
+ * for possible future use by pg_restore. We do NOT use it for determining
+ * ordering of the label in the dump file, because this routine is called
+ * after dependency sorting occurs. This routine should be called just after
+ * calling ArchiveEntry() for the specified object.
+ */
+static void
+dumpSecLabel(Archive *fout, const char *target,
+ const char *namespace, const char *owner,
+ CatalogId catalogId, int subid, DumpId dumpId)
+{
+ SecLabelItem *labels;
+ int nlabels;
+ int i;
+ PQExpBuffer query;
+
+ /* do nothing, if --no-security-labels is supplied */
+ if (no_security_labels)
+ return;
+
+ /* Comments are schema not data ... except blob comments are data */
+ if (strncmp(target, "LARGE OBJECT ", 13) != 0)
+ {
+ if (dataOnly)
+ return;
+ }
+ else
+ {
+ if (schemaOnly)
+ return;
+ }
+
+ /* Search for security labels associated with catalogId, using table */
+ nlabels = findSecLabels(fout, catalogId.tableoid, catalogId.oid, &labels);
+
+ query = createPQExpBuffer();
+
+ for (i = 0; i < nlabels; i++)
+ {
+ /*
+ * Ignore label entries for which the subid doesn't match.
+ */
+ if (labels[i].objsubid != subid)
+ continue;
+
+ appendPQExpBuffer(query,
+ "SECURITY LABEL FOR %s ON %s IS ",
+ fmtId(labels[i].provider), target);
+ appendStringLiteralAH(query, labels[i].label, fout);
+ appendPQExpBufferStr(query, ";\n");
+ }
+
+ if (query->len > 0)
+ {
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ target, namespace, NULL, owner,
+ false, "SECURITY LABEL", SECTION_NONE,
+ query->data, "", NULL,
+ &(dumpId), 1,
+ NULL, NULL);
+ }
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpTableSecLabel
+ *
+ * As above, but dump security label for both the specified table (or view)
+ * and its columns.
+ */
+static void
+dumpTableSecLabel(Archive *fout, TableInfo *tbinfo, const char *reltypename)
+{
+ SecLabelItem *labels;
+ int nlabels;
+ int i;
+ PQExpBuffer query;
+ PQExpBuffer target;
+
+ /* do nothing, if --no-security-labels is supplied */
+ if (no_security_labels)
+ return;
+
+ /* SecLabel are SCHEMA not data */
+ if (dataOnly)
+ return;
+
+ /* Search for comments associated with relation, using table */
+ nlabels = findSecLabels(fout,
+ tbinfo->dobj.catId.tableoid,
+ tbinfo->dobj.catId.oid,
+ &labels);
+
+ /* If security labels exist, build SECURITY LABEL statements */
+ if (nlabels <= 0)
+ return;
+
+ query = createPQExpBuffer();
+ target = createPQExpBuffer();
+
+ for (i = 0; i < nlabels; i++)
+ {
+ const char *colname;
+ const char *provider = labels[i].provider;
+ const char *label = labels[i].label;
+ int objsubid = labels[i].objsubid;
+
+ resetPQExpBuffer(target);
+ if (objsubid == 0)
+ {
+ appendPQExpBuffer(target, "%s %s", reltypename,
+ fmtId(tbinfo->dobj.name));
+ }
+ else
+ {
+ colname = getAttrName(objsubid, tbinfo);
+ /* first fmtId result must be consumed before calling it again */
+ appendPQExpBuffer(target, "COLUMN %s", fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(target, ".%s", fmtId(colname));
+ }
+ appendPQExpBuffer(query, "SECURITY LABEL FOR %s ON %s IS ",
+ fmtId(provider), target->data);
+ appendStringLiteralAH(query, label, fout);
+ appendPQExpBufferStr(query, ";\n");
+ }
+ if (query->len > 0)
+ {
+ resetPQExpBuffer(target);
+ appendPQExpBuffer(target, "%s %s", reltypename,
+ fmtId(tbinfo->dobj.name));
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ target->data,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL, tbinfo->rolname,
+ false, "SECURITY LABEL", SECTION_NONE,
+ query->data, "", NULL,
+ &(tbinfo->dobj.dumpId), 1,
+ NULL, NULL);
+ }
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(target);
+}
+
+/*
+ * findSecLabels
+ *
+ * Find the security label(s), if any, associated with the given object.
+ * All the objsubid values associated with the given classoid/objoid are
+ * found with one search.
+ */
+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 */
+ {
+ *items = NULL;
+ return 0;
+ }
+
+ /*
+ * Do binary search to find some item matching the object.
+ */
+ low = &labels[0];
+ high = &labels[nlabels - 1];
+ while (low <= high)
+ {
+ middle = low + (high - low) / 2;
+
+ if (classoid < middle->classoid)
+ high = middle - 1;
+ else if (classoid > middle->classoid)
+ low = middle + 1;
+ else if (objoid < middle->objoid)
+ high = middle - 1;
+ else if (objoid > middle->objoid)
+ low = middle + 1;
+ else
+ break; /* found a match */
+ }
+
+ if (low > high) /* no matches */
+ {
+ *items = NULL;
+ return 0;
+ }
+
+ /*
+ * Now determine how many items match the object. The search loop
+ * invariant still holds: only items between low and high inclusive could
+ * match.
+ */
+ nmatch = 1;
+ while (middle > low)
+ {
+ if (classoid != middle[-1].classoid ||
+ objoid != middle[-1].objoid)
+ break;
+ middle--;
+ nmatch++;
+ }
+
+ *items = middle;
+
+ middle += nmatch;
+ while (middle <= high)
+ {
+ if (classoid != middle->classoid ||
+ objoid != middle->objoid)
+ break;
+ middle++;
+ nmatch++;
+ }
+
+ return nmatch;
+}
+
+/*
+ * collectSecLabels
+ *
+ * Construct a table of all security labels available for database objects.
+ * It's much faster to pull them all at once.
+ *
+ * The table is sorted by classoid/objid/objsubid for speed in lookup.
+ */
+static int
+collectSecLabels(Archive *fout, SecLabelItem **items)
+{
+ PGresult *res;
+ PQExpBuffer query;
+ int i_label;
+ int i_provider;
+ int i_classoid;
+ int i_objoid;
+ int i_objsubid;
+ int ntups;
+ int i;
+ SecLabelItem *labels;
+
+ query = createPQExpBuffer();
+
+ appendPQExpBufferStr(query,
+ "SELECT label, provider, classoid, objoid, objsubid "
+ "FROM pg_catalog.pg_seclabel "
+ "ORDER BY classoid, objoid, objsubid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ /* Construct lookup table containing OIDs in numeric form */
+ i_label = PQfnumber(res, "label");
+ i_provider = PQfnumber(res, "provider");
+ i_classoid = PQfnumber(res, "classoid");
+ i_objoid = PQfnumber(res, "objoid");
+ i_objsubid = PQfnumber(res, "objsubid");
+
+ ntups = PQntuples(res);
+
+ labels = (SecLabelItem *) pg_malloc(ntups * sizeof(SecLabelItem));
+
+ 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));
+ }
+
+ /* Do NOT free the PGresult since we are keeping pointers into it */
+ destroyPQExpBuffer(query);
+
+ *items = labels;
+ return ntups;
+}
+
+/*
+ * dumpTable
+ * write out to fout the declarations (not data) of a user-defined table
+ */
+static void
+dumpTable(Archive *fout, TableInfo *tbinfo)
+{
+ if (tbinfo->dobj.dump && !dataOnly)
+ {
+ char *namecopy;
+
+ if (tbinfo->relkind == RELKIND_SEQUENCE)
+ dumpSequence(fout, tbinfo);
+ else
+ dumpTableSchema(fout, tbinfo);
+
+ /* Handle the ACL here */
+ namecopy = pg_strdup(fmtId(tbinfo->dobj.name));
+ dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
+ (tbinfo->relkind == RELKIND_SEQUENCE) ? "SEQUENCE" :
+ "TABLE",
+ namecopy, NULL, tbinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
+ tbinfo->relacl);
+
+ /*
+ * 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.
+ */
+ if (fout->remoteVersion >= 80400)
+ {
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ int i;
+
+ appendPQExpBuffer(query,
+ "SELECT attname, attacl FROM pg_catalog.pg_attribute "
+ "WHERE attrelid = '%u' AND NOT attisdropped AND attacl IS NOT NULL "
+ "ORDER BY attnum",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ char *attname = PQgetvalue(res, i, 0);
+ char *attacl = PQgetvalue(res, i, 1);
+ char *attnamecopy;
+ char *acltag;
+
+ attnamecopy = pg_strdup(fmtId(attname));
+ acltag = psprintf("%s.%s", tbinfo->dobj.name, attname);
+ /* Column's GRANT type is always TABLE */
+ dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId, "TABLE",
+ namecopy, attnamecopy, acltag,
+ tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
+ attacl);
+ free(attnamecopy);
+ free(acltag);
+ }
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ }
+
+ free(namecopy);
+ }
+}
+
+/*
+ * Create the AS clause for a view or materialized view. The semicolon is
+ * stripped because a materialized view must add a WITH NO DATA clause.
+ *
+ * This returns a new buffer which must be freed by the caller.
+ */
+static PQExpBuffer
+createViewAsClause(Archive *fout, TableInfo *tbinfo)
+{
+ PQExpBuffer query = createPQExpBuffer();
+ PQExpBuffer result = createPQExpBuffer();
+ PGresult *res;
+ int len;
+
+ /* Fetch the view definition */
+ if (fout->remoteVersion >= 70300)
+ {
+ /* Beginning in 7.3, viewname is not unique; rely on OID */
+ appendPQExpBuffer(query,
+ "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBufferStr(query, "SELECT definition AS viewdef "
+ "FROM pg_views WHERE viewname = ");
+ appendStringLiteralAH(query, tbinfo->dobj.name, fout);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != 1)
+ {
+ if (PQntuples(res) < 1)
+ exit_horribly(NULL, "query to obtain definition of view \"%s\" returned no data\n",
+ tbinfo->dobj.name);
+ else
+ exit_horribly(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n",
+ tbinfo->dobj.name);
+ }
+
+ len = PQgetlength(res, 0, 0);
+
+ if (len == 0)
+ exit_horribly(NULL, "definition of view \"%s\" appears to be empty (length zero)\n",
+ tbinfo->dobj.name);
+
+ /* Strip off the trailing semicolon so that other things may follow. */
+ Assert(PQgetvalue(res, 0, 0)[len - 1] == ';');
+ appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1);
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ return result;
+}
+
+/*
+ * dumpTableSchema
+ * write the declaration (not data) of one user-defined table or view
+ */
+static void
+dumpTableSchema(Archive *fout, TableInfo *tbinfo)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ PQExpBuffer delq = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+ int numParents;
+ TableInfo **parents;
+ int actual_atts; /* number of attrs in this CREATE statement */
+ const char *reltypename;
+ char *storage;
+ char *srvname;
+ char *ftoptions;
+ int j,
+ k;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ if (binary_upgrade)
+ binary_upgrade_set_type_oids_by_rel_oid(fout, q,
+ tbinfo->dobj.catId.oid);
+
+ /* Is it a table or a view? */
+ if (tbinfo->relkind == RELKIND_VIEW)
+ {
+ PQExpBuffer result;
+
+ reltypename = "VIEW";
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP VIEW %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_set_pg_class_oids(fout, q,
+ tbinfo->dobj.catId.oid, false);
+
+ appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
+ if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
+ appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
+ result = createViewAsClause(fout, tbinfo);
+ appendPQExpBuffer(q, " AS\n%s", result->data);
+ destroyPQExpBuffer(result);
+
+ if (tbinfo->checkoption != NULL)
+ appendPQExpBuffer(q, "\n WITH %s CHECK OPTION", tbinfo->checkoption);
+ appendPQExpBufferStr(q, ";\n");
+
+ appendPQExpBuffer(labelq, "VIEW %s",
+ fmtId(tbinfo->dobj.name));
+ }
+ else
+ {
+ switch (tbinfo->relkind)
+ {
+ case (RELKIND_FOREIGN_TABLE):
+ {
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ int i_srvname;
+ int i_ftoptions;
+
+ reltypename = "FOREIGN TABLE";
+
+ /* retrieve name of foreign server and generic options */
+ appendPQExpBuffer(query,
+ "SELECT fs.srvname, "
+ "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(ftoptions) "
+ "ORDER BY option_name"
+ "), E',\n ') AS ftoptions "
+ "FROM pg_catalog.pg_foreign_table ft "
+ "JOIN pg_catalog.pg_foreign_server fs "
+ "ON (fs.oid = ft.ftserver) "
+ "WHERE ft.ftrelid = '%u'",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+ i_srvname = PQfnumber(res, "srvname");
+ i_ftoptions = PQfnumber(res, "ftoptions");
+ srvname = pg_strdup(PQgetvalue(res, 0, i_srvname));
+ ftoptions = pg_strdup(PQgetvalue(res, 0, i_ftoptions));
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ break;
+ }
+ case (RELKIND_MATVIEW):
+ reltypename = "MATERIALIZED VIEW";
+ srvname = NULL;
+ ftoptions = NULL;
+ break;
+ default:
+ reltypename = "TABLE";
+ srvname = NULL;
+ ftoptions = NULL;
+ }
+
+ numParents = tbinfo->numParents;
+ parents = tbinfo->parents;
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP %s %s.", reltypename,
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ appendPQExpBuffer(labelq, "%s %s", reltypename,
+ fmtId(tbinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_set_pg_class_oids(fout, q,
+ tbinfo->dobj.catId.oid, false);
+
+ appendPQExpBuffer(q, "CREATE %s%s %s",
+ tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
+ "UNLOGGED " : "",
+ reltypename,
+ fmtId(tbinfo->dobj.name));
+
+ /*
+ * 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 && !binary_upgrade)
+ appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+
+ if (tbinfo->relkind != RELKIND_MATVIEW)
+ {
+ /* Dump the attributes */
+ actual_atts = 0;
+ for (j = 0; j < tbinfo->numatts; j++)
+ {
+ /*
+ * Normally, dump if it's locally defined in this table, and
+ * not dropped. But for binary upgrade, we'll dump all the
+ * columns, and then fix up the dropped and nonlocal cases
+ * below.
+ */
+ if (shouldPrintColumn(tbinfo, j))
+ {
+ /*
+ * Default value --- suppress if to be printed separately.
+ */
+ bool has_default = (tbinfo->attrdefs[j] != NULL &&
+ !tbinfo->attrdefs[j]->separate);
+
+ /*
+ * Not Null constraint --- suppress if inherited, except
+ * in binary-upgrade case where that won't work.
+ */
+ bool has_notnull = (tbinfo->notnull[j] &&
+ (!tbinfo->inhNotNull[j] ||
+ binary_upgrade));
+
+ /* Skip column if fully defined by reloftype */
+ if (tbinfo->reloftype &&
+ !has_default && !has_notnull && !binary_upgrade)
+ continue;
+
+ /* Format properly if not first attr */
+ if (actual_atts == 0)
+ appendPQExpBufferStr(q, " (");
+ else
+ appendPQExpBufferStr(q, ",");
+ appendPQExpBufferStr(q, "\n ");
+ actual_atts++;
+
+ /* Attribute name */
+ appendPQExpBufferStr(q, fmtId(tbinfo->attnames[j]));
+
+ if (tbinfo->attisdropped[j])
+ {
+ /*
+ * ALTER TABLE DROP COLUMN clears
+ * pg_attribute.atttypid, so we will not have gotten a
+ * valid type name; insert INTEGER as a stopgap. We'll
+ * clean things up later.
+ */
+ appendPQExpBufferStr(q, " INTEGER /* dummy */");
+ /* Skip all the rest, too */
+ continue;
+ }
+
+ /* Attribute type */
+ if (tbinfo->reloftype && !binary_upgrade)
+ {
+ appendPQExpBufferStr(q, " WITH OPTIONS");
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(q, " %s",
+ tbinfo->atttypnames[j]);
+ }
+ else
+ {
+ /* If no format_type, fake it */
+ appendPQExpBuffer(q, " %s",
+ myFormatType(tbinfo->atttypnames[j],
+ tbinfo->atttypmod[j]));
+ }
+
+ /* Add collation if not default for the type */
+ if (OidIsValid(tbinfo->attcollation[j]))
+ {
+ CollInfo *coll;
+
+ coll = findCollationByOid(tbinfo->attcollation[j]);
+ if (coll)
+ {
+ /* always schema-qualify, don't try to be smart */
+ appendPQExpBuffer(q, " COLLATE %s.",
+ fmtId(coll->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(coll->dobj.name));
+ }
+ }
+
+ if (has_default)
+ appendPQExpBuffer(q, " DEFAULT %s",
+ tbinfo->attrdefs[j]->adef_expr);
+
+ if (has_notnull)
+ appendPQExpBufferStr(q, " NOT NULL");
+ }
+ }
+
+ /*
+ * Add non-inherited CHECK constraints, if any.
+ */
+ for (j = 0; j < tbinfo->ncheck; j++)
+ {
+ ConstraintInfo *constr = &(tbinfo->checkexprs[j]);
+
+ if (constr->separate || !constr->conislocal)
+ continue;
+
+ if (actual_atts == 0)
+ appendPQExpBufferStr(q, " (\n ");
+ else
+ appendPQExpBufferStr(q, ",\n ");
+
+ appendPQExpBuffer(q, "CONSTRAINT %s ",
+ fmtId(constr->dobj.name));
+ appendPQExpBufferStr(q, constr->condef);
+
+ actual_atts++;
+ }
+
+ if (actual_atts)
+ appendPQExpBufferStr(q, "\n)");
+ else if (!(tbinfo->reloftype && !binary_upgrade))
+ {
+ /*
+ * We must have a parenthesized attribute list, even though
+ * empty, when not using the OF TYPE syntax.
+ */
+ appendPQExpBufferStr(q, " (\n)");
+ }
+
+ if (numParents > 0 && !binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\nINHERITS (");
+ for (k = 0; k < numParents; k++)
+ {
+ TableInfo *parentRel = parents[k];
+
+ if (k > 0)
+ appendPQExpBufferStr(q, ", ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+ appendPQExpBufferChar(q, ')');
+ }
+
+ if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
+ appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
+ }
+
+ if ((tbinfo->reloptions && strlen(tbinfo->reloptions) > 0) ||
+ (tbinfo->toast_reloptions && strlen(tbinfo->toast_reloptions) > 0))
+ {
+ bool addcomma = false;
+
+ appendPQExpBufferStr(q, "\nWITH (");
+ if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
+ {
+ addcomma = true;
+ appendPQExpBufferStr(q, tbinfo->reloptions);
+ }
+ if (tbinfo->toast_reloptions && strlen(tbinfo->toast_reloptions) > 0)
+ {
+ appendPQExpBuffer(q, "%s%s", addcomma ? ", " : "",
+ tbinfo->toast_reloptions);
+ }
+ appendPQExpBufferChar(q, ')');
+ }
+
+ /* Dump generic options if any */
+ if (ftoptions && ftoptions[0])
+ appendPQExpBuffer(q, "\nOPTIONS (\n %s\n)", ftoptions);
+
+ /*
+ * For materialized views, create the AS clause just like a view. At
+ * this point, we always mark the view as not populated.
+ */
+ if (tbinfo->relkind == RELKIND_MATVIEW)
+ {
+ PQExpBuffer result;
+
+ result = createViewAsClause(fout, tbinfo);
+ appendPQExpBuffer(q, " AS\n%s\n WITH NO DATA;\n",
+ result->data);
+ destroyPQExpBuffer(result);
+ }
+ else
+ appendPQExpBufferStr(q, ";\n");
+
+ /*
+ * To create binary-compatible heap files, we have to ensure the same
+ * physical column order, including dropped columns, as in the
+ * original. Therefore, we create dropped columns above and drop them
+ * here, also updating their attlen/attalign values so that the
+ * dropped column can be skipped properly. (We do not bother with
+ * restoring the original attbyval setting.) Also, inheritance
+ * relationships are set up by doing ALTER INHERIT rather than using
+ * an INHERITS clause --- the latter would possibly mess up the column
+ * order. That also means we have to take care about setting
+ * attislocal correctly, plus fix up any inherited CHECK constraints.
+ * Analogously, we set up typed tables using ALTER TABLE / OF here.
+ */
+ if (binary_upgrade && (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_FOREIGN_TABLE))
+ {
+ for (j = 0; j < tbinfo->numatts; j++)
+ {
+ if (tbinfo->attisdropped[j])
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate dropped column.\n");
+ appendPQExpBuffer(q, "UPDATE pg_catalog.pg_attribute\n"
+ "SET attlen = %d, "
+ "attalign = '%c', attbyval = false\n"
+ "WHERE attname = ",
+ tbinfo->attlen[j],
+ tbinfo->attalign[j]);
+ appendStringLiteralAH(q, tbinfo->attnames[j], fout);
+ appendPQExpBufferStr(q, "\n AND attrelid = ");
+ appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
+ appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
+
+ if (tbinfo->relkind == RELKIND_RELATION)
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ else
+ appendPQExpBuffer(q, "ALTER FOREIGN TABLE %s ",
+ fmtId(tbinfo->dobj.name));
+
+ appendPQExpBuffer(q, "DROP COLUMN %s;\n",
+ fmtId(tbinfo->attnames[j]));
+ }
+ else if (!tbinfo->attislocal[j])
+ {
+ Assert(tbinfo->relkind != RELKIND_FOREIGN_TABLE);
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate inherited column.\n");
+ appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_attribute\n"
+ "SET attislocal = false\n"
+ "WHERE attname = ");
+ appendStringLiteralAH(q, tbinfo->attnames[j], fout);
+ appendPQExpBufferStr(q, "\n AND attrelid = ");
+ appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
+ appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
+ }
+ }
+
+ for (k = 0; k < tbinfo->ncheck; k++)
+ {
+ ConstraintInfo *constr = &(tbinfo->checkexprs[k]);
+
+ if (constr->separate || constr->conislocal)
+ continue;
+
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up inherited constraint.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ADD CONSTRAINT %s ",
+ fmtId(constr->dobj.name));
+ appendPQExpBuffer(q, "%s;\n", constr->condef);
+ appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_constraint\n"
+ "SET conislocal = false\n"
+ "WHERE contype = 'c' AND conname = ");
+ appendStringLiteralAH(q, constr->dobj.name, fout);
+ appendPQExpBufferStr(q, "\n AND conrelid = ");
+ appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
+ appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
+ }
+
+ if (numParents > 0)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up inheritance this way.\n");
+ for (k = 0; k < numParents; k++)
+ {
+ TableInfo *parentRel = parents[k];
+
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s INHERIT ",
+ fmtId(tbinfo->dobj.name));
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBuffer(q, "%s;\n",
+ fmtId(parentRel->dobj.name));
+ }
+ }
+
+ if (tbinfo->reloftype)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up typed tables this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s OF %s;\n",
+ fmtId(tbinfo->dobj.name),
+ tbinfo->reloftype);
+ }
+
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
+ appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
+ "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "WHERE oid = ",
+ tbinfo->frozenxid, tbinfo->minmxid);
+ appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
+ appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
+
+ if (tbinfo->toast_oid)
+ {
+ /* We preserve the toast oids, so we can use it during restore */
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set toast's relfrozenxid and relminmxid\n");
+ appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
+ "SET relfrozenxid = '%u', relminmxid = '%u'\n"
+ "WHERE oid = '%u';\n",
+ tbinfo->toast_frozenxid,
+ tbinfo->toast_minmxid, tbinfo->toast_oid);
+ }
+ }
+
+ /*
+ * In binary_upgrade mode, restore matviews' populated status by
+ * poking pg_class directly. This is pretty ugly, but we can't use
+ * REFRESH MATERIALIZED VIEW since it's possible that some underlying
+ * matview is not populated even though this matview is.
+ */
+ if (binary_upgrade && tbinfo->relkind == RELKIND_MATVIEW &&
+ tbinfo->relispopulated)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, mark materialized view as populated\n");
+ appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_class\n"
+ "SET relispopulated = 't'\n"
+ "WHERE oid = ");
+ appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
+ appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
+ }
+
+ /*
+ * Dump additional per-column properties that we can't handle in the
+ * main CREATE TABLE command.
+ */
+ for (j = 0; j < tbinfo->numatts; j++)
+ {
+ /* None of this applies to dropped columns */
+ if (tbinfo->attisdropped[j])
+ continue;
+
+ /*
+ * If we didn't dump the column definition explicitly above, and
+ * it is NOT NULL and did not inherit that property from a parent,
+ * we have to mark it separately.
+ */
+ if (!shouldPrintColumn(tbinfo, j) &&
+ tbinfo->notnull[j] && !tbinfo->inhNotNull[j])
+ {
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, "ALTER COLUMN %s SET NOT NULL;\n",
+ fmtId(tbinfo->attnames[j]));
+ }
+
+ /*
+ * Dump per-column statistics information. We only issue an ALTER
+ * TABLE statement if the attstattarget entry for this column is
+ * non-negative (i.e. it's not the default value)
+ */
+ if (tbinfo->attstattarget[j] >= 0)
+ {
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, "ALTER COLUMN %s ",
+ fmtId(tbinfo->attnames[j]));
+ appendPQExpBuffer(q, "SET STATISTICS %d;\n",
+ tbinfo->attstattarget[j]);
+ }
+
+ /*
+ * Dump per-column storage information. The statement is only
+ * dumped if the storage has been changed from the type's default.
+ */
+ if (tbinfo->attstorage[j] != tbinfo->typstorage[j])
+ {
+ switch (tbinfo->attstorage[j])
+ {
+ case 'p':
+ storage = "PLAIN";
+ break;
+ case 'e':
+ storage = "EXTERNAL";
+ break;
+ case 'm':
+ storage = "MAIN";
+ break;
+ case 'x':
+ storage = "EXTENDED";
+ break;
+ default:
+ storage = NULL;
+ }
+
+ /*
+ * Only dump the statement if it's a storage type we recognize
+ */
+ if (storage != NULL)
+ {
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, "ALTER COLUMN %s ",
+ fmtId(tbinfo->attnames[j]));
+ appendPQExpBuffer(q, "SET STORAGE %s;\n",
+ storage);
+ }
+ }
+
+ /*
+ * Dump per-column attributes.
+ */
+ if (tbinfo->attoptions[j] && tbinfo->attoptions[j][0] != '\0')
+ {
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, "ALTER COLUMN %s ",
+ fmtId(tbinfo->attnames[j]));
+ appendPQExpBuffer(q, "SET (%s);\n",
+ tbinfo->attoptions[j]);
+ }
+
+ /*
+ * Dump per-column fdw options.
+ */
+ if (tbinfo->relkind == RELKIND_FOREIGN_TABLE &&
+ tbinfo->attfdwoptions[j] &&
+ tbinfo->attfdwoptions[j][0] != '\0')
+ {
+ appendPQExpBuffer(q, "ALTER FOREIGN TABLE %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, "ALTER COLUMN %s ",
+ fmtId(tbinfo->attnames[j]));
+ appendPQExpBuffer(q, "OPTIONS (\n %s\n);\n",
+ tbinfo->attfdwoptions[j]);
+ }
+ }
+ }
+
+ /*
+ * dump properties we only have ALTER TABLE syntax for
+ */
+ if ((tbinfo->relkind == RELKIND_RELATION || tbinfo->relkind == RELKIND_MATVIEW) &&
+ tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
+ {
+ if (tbinfo->relreplident == REPLICA_IDENTITY_INDEX)
+ {
+ /* nothing to do, will be set when the index is dumped */
+ }
+ else if (tbinfo->relreplident == REPLICA_IDENTITY_NOTHING)
+ {
+ appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY NOTHING;\n",
+ fmtId(tbinfo->dobj.name));
+ }
+ else if (tbinfo->relreplident == REPLICA_IDENTITY_FULL)
+ {
+ appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY FULL;\n",
+ fmtId(tbinfo->dobj.name));
+ }
+ }
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
+
+ ArchiveEntry(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
+ tbinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ (tbinfo->relkind == RELKIND_VIEW) ? NULL : tbinfo->reltablespace,
+ tbinfo->rolname,
+ (strcmp(reltypename, "TABLE") == 0) ? tbinfo->hasoids : false,
+ reltypename,
+ tbinfo->postponed_def ? SECTION_POST_DATA : SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+
+ /* Dump Table Comments */
+ dumpTableComment(fout, tbinfo, reltypename);
+
+ /* Dump Table Security Labels */
+ dumpTableSecLabel(fout, tbinfo, reltypename);
+
+ /* Dump comments on inlined table constraints */
+ for (j = 0; j < tbinfo->ncheck; j++)
+ {
+ ConstraintInfo *constr = &(tbinfo->checkexprs[j]);
+
+ if (constr->separate || !constr->conislocal)
+ continue;
+
+ dumpTableConstraintComment(fout, constr);
+ }
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpAttrDef --- dump an attribute's default-value declaration
+ */
+static void
+dumpAttrDef(Archive *fout, AttrDefInfo *adinfo)
+{
+ TableInfo *tbinfo = adinfo->adtable;
+ int adnum = adinfo->adnum;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+
+ /* Skip if table definition not to be dumped */
+ if (!tbinfo->dobj.dump || dataOnly)
+ return;
+
+ /* Skip if not "separate"; it was dumped in the table's definition */
+ if (!adinfo->separate)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, "ALTER COLUMN %s SET DEFAULT %s;\n",
+ fmtId(tbinfo->attnames[adnum - 1]),
+ adinfo->adef_expr);
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delq, "ALTER TABLE %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(delq, "ALTER COLUMN %s DROP DEFAULT;\n",
+ fmtId(tbinfo->attnames[adnum - 1]));
+
+ ArchiveEntry(fout, adinfo->dobj.catId, adinfo->dobj.dumpId,
+ tbinfo->attnames[adnum - 1],
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname,
+ false, "DEFAULT", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+}
+
+/*
+ * getAttrName: extract the correct name for an attribute
+ *
+ * The array tblInfo->attnames[] only provides names of user attributes;
+ * if a system attribute number is supplied, we have to fake it.
+ * We also do a little bit of bounds checking for safety's sake.
+ */
+static const char *
+getAttrName(int attrnum, TableInfo *tblInfo)
+{
+ if (attrnum > 0 && attrnum <= tblInfo->numatts)
+ return tblInfo->attnames[attrnum - 1];
+ switch (attrnum)
+ {
+ case SelfItemPointerAttributeNumber:
+ return "ctid";
+ case ObjectIdAttributeNumber:
+ return "oid";
+ case MinTransactionIdAttributeNumber:
+ return "xmin";
+ case MinCommandIdAttributeNumber:
+ return "cmin";
+ case MaxTransactionIdAttributeNumber:
+ return "xmax";
+ case MaxCommandIdAttributeNumber:
+ return "cmax";
+ case TableOidAttributeNumber:
+ return "tableoid";
+ }
+ exit_horribly(NULL, "invalid column number %d for table \"%s\"\n",
+ attrnum, tblInfo->dobj.name);
+ return NULL; /* keep compiler quiet */
+}
+
+/*
+ * dumpIndex
+ * write out to fout a user-defined index
+ */
+static void
+dumpIndex(Archive *fout, IndxInfo *indxinfo)
+{
+ TableInfo *tbinfo = indxinfo->indextable;
+ bool is_constraint = (indxinfo->indexconstraint != 0);
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+
+ if (dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ appendPQExpBuffer(labelq, "INDEX %s",
+ fmtId(indxinfo->dobj.name));
+
+ /*
+ * If there's an associated constraint, don't dump the index per se, but
+ * do dump any comment for it. (This is safe because dependency ordering
+ * will have ensured the constraint is emitted first.) Note that the
+ * emitted comment has to be shown as depending on the constraint, not the
+ * index, in such cases.
+ */
+ if (!is_constraint)
+ {
+ if (binary_upgrade)
+ binary_upgrade_set_pg_class_oids(fout, q,
+ indxinfo->dobj.catId.oid, true);
+
+ /* Plain secondary index */
+ appendPQExpBuffer(q, "%s;\n", indxinfo->indexdef);
+
+ /* If the index is clustered, we need to record that. */
+ if (indxinfo->indisclustered)
+ {
+ appendPQExpBuffer(q, "\nALTER TABLE %s CLUSTER",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ON %s;\n",
+ fmtId(indxinfo->dobj.name));
+ }
+
+ /* If the index defines identity, we need to record that. */
+ if (indxinfo->indisreplident)
+ {
+ appendPQExpBuffer(q, "\nALTER TABLE ONLY %s REPLICA IDENTITY USING",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " INDEX %s;\n",
+ fmtId(indxinfo->dobj.name));
+ }
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP INDEX %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ fmtId(indxinfo->dobj.name));
+
+ ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId,
+ indxinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ indxinfo->tablespace,
+ tbinfo->rolname, false,
+ "INDEX", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+ }
+
+ /* Dump Index Comments */
+ dumpComment(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name,
+ tbinfo->rolname,
+ indxinfo->dobj.catId, 0,
+ is_constraint ? indxinfo->indexconstraint :
+ indxinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpConstraint
+ * write out to fout a user-defined constraint
+ */
+static void
+dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
+{
+ TableInfo *tbinfo = coninfo->contable;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+
+ /* Skip if not to be dumped */
+ if (!coninfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+
+ if (coninfo->contype == 'p' ||
+ coninfo->contype == 'u' ||
+ coninfo->contype == 'x')
+ {
+ /* Index-related constraint */
+ IndxInfo *indxinfo;
+ int k;
+
+ indxinfo = (IndxInfo *) findObjectByDumpId(coninfo->conindex);
+
+ if (indxinfo == NULL)
+ exit_horribly(NULL, "missing index for constraint \"%s\"\n",
+ coninfo->dobj.name);
+
+ if (binary_upgrade)
+ binary_upgrade_set_pg_class_oids(fout, q,
+ indxinfo->dobj.catId.oid, true);
+
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ADD CONSTRAINT %s ",
+ fmtId(coninfo->dobj.name));
+
+ if (coninfo->condef)
+ {
+ /* pg_get_constraintdef should have provided everything */
+ appendPQExpBuffer(q, "%s;\n", coninfo->condef);
+ }
+ else
+ {
+ appendPQExpBuffer(q, "%s (",
+ coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
+ for (k = 0; k < indxinfo->indnkeys; k++)
+ {
+ int indkey = (int) indxinfo->indkeys[k];
+ const char *attname;
+
+ if (indkey == InvalidAttrNumber)
+ break;
+ attname = getAttrName(indkey, tbinfo);
+
+ appendPQExpBuffer(q, "%s%s",
+ (k == 0) ? "" : ", ",
+ fmtId(attname));
+ }
+
+ appendPQExpBufferChar(q, ')');
+
+ if (indxinfo->options && strlen(indxinfo->options) > 0)
+ appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
+
+ if (coninfo->condeferrable)
+ {
+ appendPQExpBufferStr(q, " DEFERRABLE");
+ if (coninfo->condeferred)
+ appendPQExpBufferStr(q, " INITIALLY DEFERRED");
+ }
+
+ appendPQExpBufferStr(q, ";\n");
+ }
+
+ /* If the index is clustered, we need to record that. */
+ if (indxinfo->indisclustered)
+ {
+ appendPQExpBuffer(q, "\nALTER TABLE %s CLUSTER",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ON %s;\n",
+ fmtId(indxinfo->dobj.name));
+ }
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ fmtId(coninfo->dobj.name));
+
+ ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ coninfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ indxinfo->tablespace,
+ tbinfo->rolname, false,
+ "CONSTRAINT", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+ }
+ else if (coninfo->contype == 'f')
+ {
+ /*
+ * XXX Potentially wrap in a 'SET CONSTRAINTS OFF' block so that the
+ * current table data is not processed
+ */
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n",
+ fmtId(coninfo->dobj.name),
+ coninfo->condef);
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "ALTER TABLE ONLY %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ fmtId(coninfo->dobj.name));
+
+ ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ coninfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname, false,
+ "FK CONSTRAINT", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+ }
+ else if (coninfo->contype == 'c' && tbinfo)
+ {
+ /* CHECK constraint on a table */
+
+ /* Ignore if not to be dumped separately, or if it was inherited */
+ if (coninfo->separate && coninfo->conislocal)
+ {
+ /* not ONLY since we want it to propagate to children */
+ appendPQExpBuffer(q, "ALTER TABLE %s\n",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n",
+ fmtId(coninfo->dobj.name),
+ coninfo->condef);
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "ALTER TABLE %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s ",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ fmtId(coninfo->dobj.name));
+
+ ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ coninfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname, false,
+ "CHECK CONSTRAINT", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+ }
+ }
+ else if (coninfo->contype == 'c' && tbinfo == NULL)
+ {
+ /* CHECK constraint on a domain */
+ TypeInfo *tyinfo = coninfo->condomain;
+
+ /* Ignore if not to be dumped separately */
+ if (coninfo->separate)
+ {
+ appendPQExpBuffer(q, "ALTER DOMAIN %s\n",
+ fmtId(tyinfo->dobj.name));
+ appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n",
+ fmtId(coninfo->dobj.name),
+ coninfo->condef);
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "ALTER DOMAIN %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s ",
+ fmtId(tyinfo->dobj.name));
+ appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
+ fmtId(coninfo->dobj.name));
+
+ ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ coninfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "CHECK CONSTRAINT", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+ }
+ }
+ else
+ {
+ exit_horribly(NULL, "unrecognized constraint type: %c\n",
+ coninfo->contype);
+ }
+
+ /* Dump Constraint Comments --- only works for table constraints */
+ if (tbinfo && coninfo->separate)
+ dumpTableConstraintComment(fout, coninfo);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+}
+
+/*
+ * dumpTableConstraintComment --- dump a constraint's comment if any
+ *
+ * This is split out because we need the function in two different places
+ * depending on whether the constraint is dumped as part of CREATE TABLE
+ * or as a separate ALTER command.
+ */
+static void
+dumpTableConstraintComment(Archive *fout, ConstraintInfo *coninfo)
+{
+ TableInfo *tbinfo = coninfo->contable;
+ PQExpBuffer labelq = createPQExpBuffer();
+
+ appendPQExpBuffer(labelq, "CONSTRAINT %s ",
+ fmtId(coninfo->dobj.name));
+ appendPQExpBuffer(labelq, "ON %s",
+ fmtId(tbinfo->dobj.name));
+ dumpComment(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name,
+ tbinfo->rolname,
+ coninfo->dobj.catId, 0,
+ coninfo->separate ? coninfo->dobj.dumpId : tbinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * findLastBuiltInOid -
+ * find the last built in oid
+ *
+ * For 7.1 and 7.2, we do this by retrieving datlastsysoid from the
+ * pg_database entry for the current database
+ */
+static Oid
+findLastBuiltinOid_V71(Archive *fout, const char *dbname)
+{
+ PGresult *res;
+ Oid last_oid;
+ PQExpBuffer query = createPQExpBuffer();
+
+ resetPQExpBuffer(query);
+ appendPQExpBufferStr(query, "SELECT datlastsysoid from pg_database where datname = ");
+ appendStringLiteralAH(query, dbname, fout);
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+ last_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "datlastsysoid")));
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ return last_oid;
+}
+
+/*
+ * findLastBuiltInOid -
+ * find the last built in oid
+ *
+ * For 7.0, we do this by assuming that the last thing that initdb does is to
+ * create the pg_indexes view. This sucks in general, but seeing that 7.0.x
+ * initdb won't be changing anymore, it'll do.
+ */
+static Oid
+findLastBuiltinOid_V70(Archive *fout)
+{
+ PGresult *res;
+ int last_oid;
+
+ res = ExecuteSqlQueryForSingleRow(fout,
+ "SELECT oid FROM pg_class WHERE relname = 'pg_indexes'");
+ last_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+ PQclear(res);
+ return last_oid;
+}
+
+/*
+ * dumpSequence
+ * write the declaration (not data) of one user-defined sequence
+ */
+static void
+dumpSequence(Archive *fout, TableInfo *tbinfo)
+{
+ PGresult *res;
+ char *startv,
+ *incby,
+ *maxv = NULL,
+ *minv = NULL,
+ *cache;
+ char bufm[100],
+ bufx[100];
+ bool cycled;
+ PQExpBuffer query = createPQExpBuffer();
+ PQExpBuffer delqry = createPQExpBuffer();
+ PQExpBuffer labelq = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ snprintf(bufm, sizeof(bufm), INT64_FORMAT, SEQ_MINVALUE);
+ snprintf(bufx, sizeof(bufx), INT64_FORMAT, SEQ_MAXVALUE);
+
+ if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(query,
+ "SELECT sequence_name, "
+ "start_value, increment_by, "
+ "CASE WHEN increment_by > 0 AND max_value = %s THEN NULL "
+ " WHEN increment_by < 0 AND max_value = -1 THEN NULL "
+ " ELSE max_value "
+ "END AS max_value, "
+ "CASE WHEN increment_by > 0 AND min_value = 1 THEN NULL "
+ " WHEN increment_by < 0 AND min_value = %s THEN NULL "
+ " ELSE min_value "
+ "END AS min_value, "
+ "cache_value, is_cycled FROM %s",
+ bufx, bufm,
+ fmtId(tbinfo->dobj.name));
+ }
+ else
+ {
+ appendPQExpBuffer(query,
+ "SELECT sequence_name, "
+ "0 AS start_value, increment_by, "
+ "CASE WHEN increment_by > 0 AND max_value = %s THEN NULL "
+ " WHEN increment_by < 0 AND max_value = -1 THEN NULL "
+ " ELSE max_value "
+ "END AS max_value, "
+ "CASE WHEN increment_by > 0 AND min_value = 1 THEN NULL "
+ " WHEN increment_by < 0 AND min_value = %s THEN NULL "
+ " ELSE min_value "
+ "END AS min_value, "
+ "cache_value, is_cycled FROM %s",
+ bufx, bufm,
+ fmtId(tbinfo->dobj.name));
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != 1)
+ {
+ write_msg(NULL, ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)\n",
+ "query to get data of sequence \"%s\" returned %d rows (expected 1)\n",
+ PQntuples(res)),
+ tbinfo->dobj.name, PQntuples(res));
+ exit_nicely(1);
+ }
+
+ /* Disable this check: it fails if sequence has been renamed */
+#ifdef NOT_USED
+ if (strcmp(PQgetvalue(res, 0, 0), tbinfo->dobj.name) != 0)
+ {
+ write_msg(NULL, "query to get data of sequence \"%s\" returned name \"%s\"\n",
+ tbinfo->dobj.name, PQgetvalue(res, 0, 0));
+ exit_nicely(1);
+ }
+#endif
+
+ startv = PQgetvalue(res, 0, 1);
+ incby = PQgetvalue(res, 0, 2);
+ if (!PQgetisnull(res, 0, 3))
+ maxv = PQgetvalue(res, 0, 3);
+ if (!PQgetisnull(res, 0, 4))
+ minv = PQgetvalue(res, 0, 4);
+ cache = PQgetvalue(res, 0, 5);
+ cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delqry, "DROP SEQUENCE %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delqry, "%s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ resetPQExpBuffer(query);
+
+ if (binary_upgrade)
+ {
+ binary_upgrade_set_pg_class_oids(fout, query,
+ tbinfo->dobj.catId.oid, false);
+ binary_upgrade_set_type_oids_by_rel_oid(fout, query,
+ tbinfo->dobj.catId.oid);
+ }
+
+ appendPQExpBuffer(query,
+ "CREATE SEQUENCE %s\n",
+ fmtId(tbinfo->dobj.name));
+
+ if (fout->remoteVersion >= 80400)
+ appendPQExpBuffer(query, " START WITH %s\n", startv);
+
+ appendPQExpBuffer(query, " INCREMENT BY %s\n", incby);
+
+ if (minv)
+ appendPQExpBuffer(query, " MINVALUE %s\n", minv);
+ else
+ appendPQExpBufferStr(query, " NO MINVALUE\n");
+
+ if (maxv)
+ appendPQExpBuffer(query, " MAXVALUE %s\n", maxv);
+ else
+ appendPQExpBufferStr(query, " NO MAXVALUE\n");
+
+ appendPQExpBuffer(query,
+ " CACHE %s%s",
+ cache, (cycled ? "\n CYCLE" : ""));
+
+ appendPQExpBufferStr(query, ";\n");
+
+ appendPQExpBuffer(labelq, "SEQUENCE %s", fmtId(tbinfo->dobj.name));
+
+ /* binary_upgrade: no need to clear TOAST table oid */
+
+ if (binary_upgrade)
+ binary_upgrade_extension_member(query, &tbinfo->dobj,
+ labelq->data);
+
+ ArchiveEntry(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
+ tbinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname,
+ false, "SEQUENCE", SECTION_PRE_DATA,
+ query->data, delqry->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /*
+ * If the sequence is owned by a table column, emit the ALTER for it as a
+ * separate TOC entry immediately following the sequence's own entry. It's
+ * OK to do this rather than using full sorting logic, because the
+ * dependency that tells us it's owned will have forced the table to be
+ * created first. We can't just include the ALTER in the TOC entry
+ * because it will fail if we haven't reassigned the sequence owner to
+ * match the table's owner.
+ *
+ * We need not schema-qualify the table reference because both sequence
+ * and table must be in the same schema.
+ */
+ if (OidIsValid(tbinfo->owning_tab))
+ {
+ TableInfo *owning_tab = findTableByOid(tbinfo->owning_tab);
+
+ if (owning_tab && owning_tab->dobj.dump)
+ {
+ resetPQExpBuffer(query);
+ appendPQExpBuffer(query, "ALTER SEQUENCE %s",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(query, " OWNED BY %s",
+ fmtId(owning_tab->dobj.name));
+ appendPQExpBuffer(query, ".%s;\n",
+ fmtId(owning_tab->attnames[tbinfo->owning_col - 1]));
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ tbinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname,
+ false, "SEQUENCE OWNED BY", SECTION_PRE_DATA,
+ query->data, "", NULL,
+ &(tbinfo->dobj.dumpId), 1,
+ NULL, NULL);
+ }
+ }
+
+ /* Dump Sequence Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
+ tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
+ tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(delqry);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpSequenceData
+ * write the data of one user-defined sequence
+ */
+static void
+dumpSequenceData(Archive *fout, TableDataInfo *tdinfo)
+{
+ TableInfo *tbinfo = tdinfo->tdtable;
+ PGresult *res;
+ char *last;
+ bool called;
+ PQExpBuffer query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ appendPQExpBuffer(query,
+ "SELECT last_value, is_called FROM %s",
+ fmtId(tbinfo->dobj.name));
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != 1)
+ {
+ write_msg(NULL, ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)\n",
+ "query to get data of sequence \"%s\" returned %d rows (expected 1)\n",
+ PQntuples(res)),
+ tbinfo->dobj.name, PQntuples(res));
+ exit_nicely(1);
+ }
+
+ last = PQgetvalue(res, 0, 0);
+ called = (strcmp(PQgetvalue(res, 0, 1), "t") == 0);
+
+ resetPQExpBuffer(query);
+ appendPQExpBufferStr(query, "SELECT pg_catalog.setval(");
+ appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout);
+ appendPQExpBuffer(query, ", %s, %s);\n",
+ last, (called ? "true" : "false"));
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ tbinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname,
+ false, "SEQUENCE SET", SECTION_DATA,
+ query->data, "", NULL,
+ &(tbinfo->dobj.dumpId), 1,
+ NULL, NULL);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpTrigger
+ * write the declaration of one user-defined table trigger
+ */
+static void
+dumpTrigger(Archive *fout, TriggerInfo *tginfo)
+{
+ TableInfo *tbinfo = tginfo->tgtable;
+ PQExpBuffer query;
+ PQExpBuffer delqry;
+ PQExpBuffer labelq;
+ char *tgargs;
+ size_t lentgargs;
+ const char *p;
+ int findx;
+
+ /*
+ * we needn't check dobj.dump because TriggerInfo wouldn't have been
+ * created in the first place for non-dumpable triggers
+ */
+ if (dataOnly)
+ return;
+
+ query = createPQExpBuffer();
+ delqry = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delqry, "DROP TRIGGER %s ",
+ fmtId(tginfo->dobj.name));
+ appendPQExpBuffer(delqry, "ON %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delqry, "%s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ if (tginfo->tgdef)
+ {
+ appendPQExpBuffer(query, "%s;\n", tginfo->tgdef);
+ }
+ else
+ {
+ if (tginfo->tgisconstraint)
+ {
+ appendPQExpBufferStr(query, "CREATE CONSTRAINT TRIGGER ");
+ appendPQExpBufferStr(query, fmtId(tginfo->tgconstrname));
+ }
+ else
+ {
+ appendPQExpBufferStr(query, "CREATE TRIGGER ");
+ appendPQExpBufferStr(query, fmtId(tginfo->dobj.name));
+ }
+ appendPQExpBufferStr(query, "\n ");
+
+ /* Trigger type */
+ if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
+ appendPQExpBufferStr(query, "BEFORE");
+ else if (TRIGGER_FOR_AFTER(tginfo->tgtype))
+ appendPQExpBufferStr(query, "AFTER");
+ else if (TRIGGER_FOR_INSTEAD(tginfo->tgtype))
+ appendPQExpBufferStr(query, "INSTEAD OF");
+ else
+ {
+ write_msg(NULL, "unexpected tgtype value: %d\n", tginfo->tgtype);
+ exit_nicely(1);
+ }
+
+ findx = 0;
+ if (TRIGGER_FOR_INSERT(tginfo->tgtype))
+ {
+ appendPQExpBufferStr(query, " INSERT");
+ findx++;
+ }
+ if (TRIGGER_FOR_DELETE(tginfo->tgtype))
+ {
+ if (findx > 0)
+ appendPQExpBufferStr(query, " OR DELETE");
+ else
+ appendPQExpBufferStr(query, " DELETE");
+ findx++;
+ }
+ if (TRIGGER_FOR_UPDATE(tginfo->tgtype))
+ {
+ if (findx > 0)
+ appendPQExpBufferStr(query, " OR UPDATE");
+ else
+ appendPQExpBufferStr(query, " UPDATE");
+ findx++;
+ }
+ if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
+ {
+ if (findx > 0)
+ appendPQExpBufferStr(query, " OR TRUNCATE");
+ else
+ appendPQExpBufferStr(query, " TRUNCATE");
+ findx++;
+ }
+ appendPQExpBuffer(query, " ON %s\n",
+ fmtId(tbinfo->dobj.name));
+
+ if (tginfo->tgisconstraint)
+ {
+ if (OidIsValid(tginfo->tgconstrrelid))
+ {
+ /* If we are using regclass, name is already quoted */
+ if (fout->remoteVersion >= 70300)
+ appendPQExpBuffer(query, " FROM %s\n ",
+ tginfo->tgconstrrelname);
+ else
+ appendPQExpBuffer(query, " FROM %s\n ",
+ fmtId(tginfo->tgconstrrelname));
+ }
+ if (!tginfo->tgdeferrable)
+ appendPQExpBufferStr(query, "NOT ");
+ appendPQExpBufferStr(query, "DEFERRABLE INITIALLY ");
+ if (tginfo->tginitdeferred)
+ appendPQExpBufferStr(query, "DEFERRED\n");
+ else
+ appendPQExpBufferStr(query, "IMMEDIATE\n");
+ }
+
+ if (TRIGGER_FOR_ROW(tginfo->tgtype))
+ appendPQExpBufferStr(query, " FOR EACH ROW\n ");
+ else
+ appendPQExpBufferStr(query, " FOR EACH STATEMENT\n ");
+
+ /* In 7.3, result of regproc is already quoted */
+ if (fout->remoteVersion >= 70300)
+ appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
+ tginfo->tgfname);
+ else
+ appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
+ fmtId(tginfo->tgfname));
+
+ tgargs = (char *) PQunescapeBytea((unsigned char *) tginfo->tgargs,
+ &lentgargs);
+ p = tgargs;
+ for (findx = 0; findx < tginfo->tgnargs; findx++)
+ {
+ /* find the embedded null that terminates this trigger argument */
+ size_t tlen = strlen(p);
+
+ if (p + tlen >= tgargs + lentgargs)
+ {
+ /* hm, not found before end of bytea value... */
+ write_msg(NULL, "invalid argument string (%s) for trigger \"%s\" on table \"%s\"\n",
+ tginfo->tgargs,
+ tginfo->dobj.name,
+ tbinfo->dobj.name);
+ exit_nicely(1);
+ }
+
+ if (findx > 0)
+ appendPQExpBufferStr(query, ", ");
+ appendStringLiteralAH(query, p, fout);
+ p += tlen + 1;
+ }
+ free(tgargs);
+ appendPQExpBufferStr(query, ");\n");
+ }
+
+ if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O')
+ {
+ appendPQExpBuffer(query, "\nALTER TABLE %s ",
+ fmtId(tbinfo->dobj.name));
+ switch (tginfo->tgenabled)
+ {
+ case 'D':
+ case 'f':
+ appendPQExpBufferStr(query, "DISABLE");
+ break;
+ case 'A':
+ appendPQExpBufferStr(query, "ENABLE ALWAYS");
+ break;
+ case 'R':
+ appendPQExpBufferStr(query, "ENABLE REPLICA");
+ break;
+ default:
+ appendPQExpBufferStr(query, "ENABLE");
+ break;
+ }
+ appendPQExpBuffer(query, " TRIGGER %s;\n",
+ fmtId(tginfo->dobj.name));
+ }
+
+ appendPQExpBuffer(labelq, "TRIGGER %s ",
+ fmtId(tginfo->dobj.name));
+ appendPQExpBuffer(labelq, "ON %s",
+ fmtId(tbinfo->dobj.name));
+
+ ArchiveEntry(fout, tginfo->dobj.catId, tginfo->dobj.dumpId,
+ tginfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname, false,
+ "TRIGGER", SECTION_POST_DATA,
+ query->data, delqry->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ dumpComment(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
+ tginfo->dobj.catId, 0, tginfo->dobj.dumpId);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(delqry);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpEventTrigger
+ * write the declaration of one user-defined event trigger
+ */
+static void
+dumpEventTrigger(Archive *fout, EventTriggerInfo *evtinfo)
+{
+ PQExpBuffer query;
+ PQExpBuffer labelq;
+
+ /* Skip if not to be dumped */
+ if (!evtinfo->dobj.dump || dataOnly)
+ return;
+
+ query = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ appendPQExpBufferStr(query, "CREATE EVENT TRIGGER ");
+ appendPQExpBufferStr(query, fmtId(evtinfo->dobj.name));
+ appendPQExpBufferStr(query, " ON ");
+ appendPQExpBufferStr(query, fmtId(evtinfo->evtevent));
+
+ if (strcmp("", evtinfo->evttags) != 0)
+ {
+ appendPQExpBufferStr(query, "\n WHEN TAG IN (");
+ appendPQExpBufferStr(query, evtinfo->evttags);
+ appendPQExpBufferChar(query, ')');
+ }
+
+ appendPQExpBufferStr(query, "\n EXECUTE PROCEDURE ");
+ appendPQExpBufferStr(query, evtinfo->evtfname);
+ appendPQExpBufferStr(query, "();\n");
+
+ if (evtinfo->evtenabled != 'O')
+ {
+ appendPQExpBuffer(query, "\nALTER EVENT TRIGGER %s ",
+ fmtId(evtinfo->dobj.name));
+ switch (evtinfo->evtenabled)
+ {
+ case 'D':
+ appendPQExpBufferStr(query, "DISABLE");
+ break;
+ case 'A':
+ appendPQExpBufferStr(query, "ENABLE ALWAYS");
+ break;
+ case 'R':
+ appendPQExpBufferStr(query, "ENABLE REPLICA");
+ break;
+ default:
+ appendPQExpBufferStr(query, "ENABLE");
+ break;
+ }
+ appendPQExpBufferStr(query, ";\n");
+ }
+ appendPQExpBuffer(labelq, "EVENT TRIGGER %s",
+ fmtId(evtinfo->dobj.name));
+
+ ArchiveEntry(fout, evtinfo->dobj.catId, evtinfo->dobj.dumpId,
+ evtinfo->dobj.name, NULL, NULL, evtinfo->evtowner, false,
+ "EVENT TRIGGER", SECTION_POST_DATA,
+ query->data, "", NULL, NULL, 0, NULL, NULL);
+
+ dumpComment(fout, labelq->data,
+ NULL, evtinfo->evtowner,
+ evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * dumpRule
+ * Dump a rule
+ */
+static void
+dumpRule(Archive *fout, RuleInfo *rinfo)
+{
+ TableInfo *tbinfo = rinfo->ruletable;
+ PQExpBuffer query;
+ PQExpBuffer cmd;
+ PQExpBuffer delcmd;
+ PQExpBuffer labelq;
+ PGresult *res;
+
+ /* Skip if not to be dumped */
+ if (!rinfo->dobj.dump || dataOnly)
+ return;
+
+ /*
+ * If it is an ON SELECT rule that is created implicitly by CREATE VIEW,
+ * we do not want to dump it as a separate object.
+ */
+ if (!rinfo->separate)
+ return;
+
+ /*
+ * Make sure we are in proper schema.
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ query = createPQExpBuffer();
+ cmd = createPQExpBuffer();
+ delcmd = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query,
+ "SELECT pg_catalog.pg_get_ruledef('%u'::pg_catalog.oid) AS definition",
+ rinfo->dobj.catId.oid);
+ }
+ else
+ {
+ /* Rule name was unique before 7.3 ... */
+ appendPQExpBuffer(query,
+ "SELECT pg_get_ruledef('%s') AS definition",
+ rinfo->dobj.name);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != 1)
+ {
+ write_msg(NULL, "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows returned\n",
+ rinfo->dobj.name, tbinfo->dobj.name);
+ exit_nicely(1);
+ }
+
+ printfPQExpBuffer(cmd, "%s\n", PQgetvalue(res, 0, 0));
+
+ /*
+ * Add the command to alter the rules replication firing semantics if it
+ * differs from the default.
+ */
+ if (rinfo->ev_enabled != 'O')
+ {
+ appendPQExpBuffer(cmd, "ALTER TABLE %s ", fmtId(tbinfo->dobj.name));
+ switch (rinfo->ev_enabled)
+ {
+ case 'A':
+ appendPQExpBuffer(cmd, "ENABLE ALWAYS RULE %s;\n",
+ fmtId(rinfo->dobj.name));
+ break;
+ case 'R':
+ appendPQExpBuffer(cmd, "ENABLE REPLICA RULE %s;\n",
+ fmtId(rinfo->dobj.name));
+ break;
+ case 'D':
+ appendPQExpBuffer(cmd, "DISABLE RULE %s;\n",
+ fmtId(rinfo->dobj.name));
+ break;
+ }
+ }
+
+ /*
+ * Apply view's reloptions when its ON SELECT rule is separate.
+ */
+ if (rinfo->reloptions && strlen(rinfo->reloptions) > 0)
+ {
+ appendPQExpBuffer(cmd, "ALTER VIEW %s SET (%s);\n",
+ fmtId(tbinfo->dobj.name),
+ rinfo->reloptions);
+ }
+
+ /*
+ * DROP must be fully qualified in case same name appears in pg_catalog
+ */
+ appendPQExpBuffer(delcmd, "DROP RULE %s ",
+ fmtId(rinfo->dobj.name));
+ appendPQExpBuffer(delcmd, "ON %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delcmd, "%s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ appendPQExpBuffer(labelq, "RULE %s",
+ fmtId(rinfo->dobj.name));
+ appendPQExpBuffer(labelq, " ON %s",
+ fmtId(tbinfo->dobj.name));
+
+ ArchiveEntry(fout, rinfo->dobj.catId, rinfo->dobj.dumpId,
+ rinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname, false,
+ "RULE", SECTION_POST_DATA,
+ cmd->data, delcmd->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump rule comments */
+ dumpComment(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name,
+ tbinfo->rolname,
+ rinfo->dobj.catId, 0, rinfo->dobj.dumpId);
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(cmd);
+ destroyPQExpBuffer(delcmd);
+ destroyPQExpBuffer(labelq);
+}
+
+/*
+ * getExtensionMembership --- obtain extension membership data
+ *
+ * There are three main parts to this process:
+ *
+ * 1. Identify objects which are members of extensions
+ *
+ * Generally speaking, this is to mark them as *not* being dumped, as most
+ * extension objects are created by the single CREATE EXTENSION command.
+ * The one exception is binary upgrades with pg_upgrade will still dump the
+ * non-table objects.
+ *
+ * 2. Identify and create dump records for extension configuration tables.
+ *
+ * Extensions can mark tables as "configuration", which means that the user
+ * is able and expected to modify those tables after the extension has been
+ * loaded. For these tables, we dump out only the data- the structure is
+ * expected to be handled at CREATE EXTENSION time, including any indexes or
+ * foriegn keys, which brings us to-
+ *
+ * 3. Record FK dependencies between configuration tables.
+ *
+ * Due to the FKs being created at CREATE EXTENSION time and therefore before
+ * the data is loaded, we have to work out what the best order for reloading
+ * the data is, to avoid FK violations when the tables are restored. This is
+ * not perfect- we can't handle circular dependencies and if any exist they
+ * will cause an invalid dump to be produced (though at least all of the data
+ * is included for a user to manually restore). This is currently documented
+ * but perhaps we can provide a better solution in the future.
+ */
+void
+getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
+ int numExtensions)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ int ntups,
+ i;
+ int i_classid,
+ i_objid,
+ i_refclassid,
+ i_refobjid,
+ i_conrelid,
+ i_confrelid;
+ DumpableObject *dobj,
+ *refdobj;
+
+ /* Nothing to do if no extensions */
+ if (numExtensions == 0)
+ return;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ query = createPQExpBuffer();
+
+ /* refclassid constraint is redundant but may speed the search */
+ appendPQExpBufferStr(query, "SELECT "
+ "classid, objid, refclassid, refobjid "
+ "FROM pg_depend "
+ "WHERE refclassid = 'pg_extension'::regclass "
+ "AND deptype = 'e' "
+ "ORDER BY 3,4");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_classid = PQfnumber(res, "classid");
+ i_objid = PQfnumber(res, "objid");
+ i_refclassid = PQfnumber(res, "refclassid");
+ i_refobjid = PQfnumber(res, "refobjid");
+
+ /*
+ * Since we ordered the SELECT by referenced ID, we can expect that
+ * multiple entries for the same extension will appear together; this
+ * saves on searches.
+ */
+ refdobj = NULL;
+
+ for (i = 0; i < ntups; i++)
+ {
+ CatalogId objId;
+ CatalogId refobjId;
+
+ objId.tableoid = atooid(PQgetvalue(res, i, i_classid));
+ objId.oid = atooid(PQgetvalue(res, i, i_objid));
+ refobjId.tableoid = atooid(PQgetvalue(res, i, i_refclassid));
+ refobjId.oid = atooid(PQgetvalue(res, i, i_refobjid));
+
+ if (refdobj == NULL ||
+ refdobj->catId.tableoid != refobjId.tableoid ||
+ refdobj->catId.oid != refobjId.oid)
+ refdobj = findObjectByCatalogId(refobjId);
+
+ /*
+ * Failure to find objects mentioned in pg_depend is not unexpected,
+ * since for example we don't collect info about TOAST tables.
+ */
+ if (refdobj == NULL)
+ {
+#ifdef NOT_USED
+ fprintf(stderr, "no referenced object %u %u\n",
+ refobjId.tableoid, refobjId.oid);
+#endif
+ continue;
+ }
+
+ dobj = findObjectByCatalogId(objId);
+
+ if (dobj == NULL)
+ {
+#ifdef NOT_USED
+ fprintf(stderr, "no referencing object %u %u\n",
+ objId.tableoid, objId.oid);
+#endif
+ continue;
+ }
+
+ /* Record dependency so that getDependencies needn't repeat this */
+ addObjectDependency(dobj, refdobj->dumpId);
+
+ dobj->ext_member = true;
+
+ /*
+ * Normally, mark the member object as not to be dumped. But in
+ * binary upgrades, we still dump the members individually, since the
+ * idea is to exactly reproduce the database contents rather than
+ * replace the extension contents with something different.
+ */
+ if (!binary_upgrade)
+ dobj->dump = false;
+ else
+ dobj->dump = refdobj->dump;
+ }
+
+ PQclear(res);
+
+ /*
+ * Now identify extension configuration tables and create TableDataInfo
+ * objects for them, ensuring their data will be dumped even though the
+ * tables themselves won't be.
+ *
+ * Note that we create TableDataInfo objects even in schemaOnly mode, ie,
+ * user data in a configuration table is treated like schema data. This
+ * seems appropriate since system data in a config table would get
+ * reloaded by CREATE EXTENSION.
+ */
+ for (i = 0; i < numExtensions; i++)
+ {
+ ExtensionInfo *curext = &(extinfo[i]);
+ char *extconfig = curext->extconfig;
+ char *extcondition = curext->extcondition;
+ char **extconfigarray = NULL;
+ char **extconditionarray = NULL;
+ int nconfigitems;
+ int nconditionitems;
+
+ if (parsePGArray(extconfig, &extconfigarray, &nconfigitems) &&
+ parsePGArray(extcondition, &extconditionarray, &nconditionitems) &&
+ nconfigitems == nconditionitems)
+ {
+ int j;
+
+ for (j = 0; j < nconfigitems; j++)
+ {
+ TableInfo *configtbl;
+ Oid configtbloid = atooid(extconfigarray[j]);
+ bool dumpobj = curext->dobj.dump;
+
+ configtbl = findTableByOid(configtbloid);
+ if (configtbl == NULL)
+ continue;
+
+ /*
+ * Tables of not-to-be-dumped extensions shouldn't be dumped
+ * unless the table or its schema is explicitly included
+ */
+ if (!curext->dobj.dump)
+ {
+ /* check table explicitly requested */
+ if (table_include_oids.head != NULL &&
+ simple_oid_list_member(&table_include_oids,
+ configtbloid))
+ dumpobj = true;
+
+ /* check table's schema explicitly requested */
+ if (configtbl->dobj.namespace->dobj.dump)
+ dumpobj = true;
+ }
+
+ /* check table excluded by an exclusion switch */
+ if (table_exclude_oids.head != NULL &&
+ simple_oid_list_member(&table_exclude_oids,
+ configtbloid))
+ dumpobj = false;
+
+ /* check schema excluded by an exclusion switch */
+ if (simple_oid_list_member(&schema_exclude_oids,
+ configtbl->dobj.namespace->dobj.catId.oid))
+ dumpobj = false;
+
+ if (dumpobj)
+ {
+ /*
+ * Note: config tables are dumped without OIDs regardless
+ * of the --oids setting. This is because row filtering
+ * conditions aren't compatible with dumping OIDs.
+ */
+ makeTableDataInfo(configtbl, false);
+ if (configtbl->dataObj != NULL)
+ {
+ if (strlen(extconditionarray[j]) > 0)
+ configtbl->dataObj->filtercond = pg_strdup(extconditionarray[j]);
+ }
+ }
+ }
+ }
+ if (extconfigarray)
+ free(extconfigarray);
+ if (extconditionarray)
+ free(extconditionarray);
+ }
+
+ /*
+ * Now that all the TableInfoData objects have been created for all
+ * the extensions, check their FK dependencies and register them to
+ * try and dump the data out in an order which they can be restored
+ * in.
+ *
+ * Note that this is not a problem for user tables as their FKs are
+ * recreated after the data has been loaded.
+ */
+ printfPQExpBuffer(query,
+ "SELECT conrelid, confrelid "
+ "FROM pg_constraint "
+ "JOIN pg_depend ON (objid = confrelid) "
+ "WHERE contype = 'f' "
+ "AND refclassid = 'pg_extension'::regclass "
+ "AND classid = 'pg_class'::regclass;");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+
+ i_conrelid = PQfnumber(res, "conrelid");
+ i_confrelid = PQfnumber(res, "confrelid");
+
+ /* Now get the dependencies and register them */
+ for (i = 0; i < ntups; i++)
+ {
+ Oid conrelid, confrelid;
+ TableInfo *reftable, *contable;
+
+ conrelid = atooid(PQgetvalue(res, i, i_conrelid));
+ confrelid = atooid(PQgetvalue(res, i, i_confrelid));
+ contable = findTableByOid(conrelid);
+ reftable = findTableByOid(confrelid);
+
+ if (reftable == NULL ||
+ reftable->dataObj == NULL ||
+ contable == NULL ||
+ contable->dataObj == NULL)
+ continue;
+
+ /*
+ * Make referencing TABLE_DATA object depend on the
+ * referenced table's TABLE_DATA object.
+ */
+ addObjectDependency(&contable->dataObj->dobj,
+ reftable->dataObj->dobj.dumpId);
+ }
+ PQclear(res);
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getDependencies --- obtain available dependency data
+ */
+static void
+getDependencies(Archive *fout)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ int ntups,
+ i;
+ int i_classid,
+ i_objid,
+ i_refclassid,
+ i_refobjid,
+ i_deptype;
+ DumpableObject *dobj,
+ *refdobj;
+
+ /* No dependency info available before 7.3 */
+ if (fout->remoteVersion < 70300)
+ return;
+
+ if (g_verbose)
+ write_msg(NULL, "reading dependency data\n");
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ query = createPQExpBuffer();
+
+ /*
+ * PIN dependencies aren't interesting, and EXTENSION dependencies were
+ * already processed by getExtensionMembership.
+ */
+ appendPQExpBufferStr(query, "SELECT "
+ "classid, objid, refclassid, refobjid, deptype "
+ "FROM pg_depend "
+ "WHERE deptype != 'p' AND deptype != 'e' "
+ "ORDER BY 1,2");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_classid = PQfnumber(res, "classid");
+ i_objid = PQfnumber(res, "objid");
+ i_refclassid = PQfnumber(res, "refclassid");
+ i_refobjid = PQfnumber(res, "refobjid");
+ i_deptype = PQfnumber(res, "deptype");
+
+ /*
+ * Since we ordered the SELECT by referencing ID, we can expect that
+ * multiple entries for the same object will appear together; this saves
+ * on searches.
+ */
+ dobj = NULL;
+
+ for (i = 0; i < ntups; i++)
+ {
+ CatalogId objId;
+ CatalogId refobjId;
+ char deptype;
+
+ objId.tableoid = atooid(PQgetvalue(res, i, i_classid));
+ objId.oid = atooid(PQgetvalue(res, i, i_objid));
+ refobjId.tableoid = atooid(PQgetvalue(res, i, i_refclassid));
+ refobjId.oid = atooid(PQgetvalue(res, i, i_refobjid));
+ deptype = *(PQgetvalue(res, i, i_deptype));
+
+ if (dobj == NULL ||
+ dobj->catId.tableoid != objId.tableoid ||
+ dobj->catId.oid != objId.oid)
+ dobj = findObjectByCatalogId(objId);
+
+ /*
+ * Failure to find objects mentioned in pg_depend is not unexpected,
+ * since for example we don't collect info about TOAST tables.
+ */
+ if (dobj == NULL)
+ {
+#ifdef NOT_USED
+ fprintf(stderr, "no referencing object %u %u\n",
+ objId.tableoid, objId.oid);
+#endif
+ continue;
+ }
+
+ refdobj = findObjectByCatalogId(refobjId);
+
+ if (refdobj == NULL)
+ {
+#ifdef NOT_USED
+ fprintf(stderr, "no referenced object %u %u\n",
+ refobjId.tableoid, refobjId.oid);
+#endif
+ continue;
+ }
+
+ /*
+ * Ordinarily, table rowtypes have implicit dependencies on their
+ * tables. However, for a composite type the implicit dependency goes
+ * the other way in pg_depend; which is the right thing for DROP but
+ * it doesn't produce the dependency ordering we need. So in that one
+ * case, we reverse the direction of the dependency.
+ */
+ if (deptype == 'i' &&
+ dobj->objType == DO_TABLE &&
+ refdobj->objType == DO_TYPE)
+ addObjectDependency(refdobj, dobj->dumpId);
+ else
+ /* normal case */
+ addObjectDependency(dobj, refdobj->dumpId);
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+}
+
+
+/*
+ * createBoundaryObjects - create dummy DumpableObjects to represent
+ * dump section boundaries.
+ */
+static DumpableObject *
+createBoundaryObjects(void)
+{
+ DumpableObject *dobjs;
+
+ dobjs = (DumpableObject *) pg_malloc(2 * sizeof(DumpableObject));
+
+ dobjs[0].objType = DO_PRE_DATA_BOUNDARY;
+ dobjs[0].catId = nilCatalogId;
+ AssignDumpId(dobjs + 0);
+ dobjs[0].name = pg_strdup("PRE-DATA BOUNDARY");
+
+ dobjs[1].objType = DO_POST_DATA_BOUNDARY;
+ dobjs[1].catId = nilCatalogId;
+ AssignDumpId(dobjs + 1);
+ dobjs[1].name = pg_strdup("POST-DATA BOUNDARY");
+
+ return dobjs;
+}
+
+/*
+ * addBoundaryDependencies - add dependencies as needed to enforce the dump
+ * section boundaries.
+ */
+static void
+addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
+ DumpableObject *boundaryObjs)
+{
+ DumpableObject *preDataBound = boundaryObjs + 0;
+ DumpableObject *postDataBound = boundaryObjs + 1;
+ int i;
+
+ for (i = 0; i < numObjs; i++)
+ {
+ DumpableObject *dobj = dobjs[i];
+
+ /*
+ * The classification of object types here must match the SECTION_xxx
+ * values assigned during subsequent ArchiveEntry calls!
+ */
+ switch (dobj->objType)
+ {
+ case DO_NAMESPACE:
+ case DO_EXTENSION:
+ case DO_TYPE:
+ case DO_SHELL_TYPE:
+ case DO_FUNC:
+ case DO_AGG:
+ case DO_OPERATOR:
+ case DO_OPCLASS:
+ case DO_OPFAMILY:
+ case DO_COLLATION:
+ case DO_CONVERSION:
+ case DO_TABLE:
+ case DO_ATTRDEF:
+ case DO_PROCLANG:
+ case DO_CAST:
+ case DO_DUMMY_TYPE:
+ case DO_TSPARSER:
+ case DO_TSDICT:
+ case DO_TSTEMPLATE:
+ case DO_TSCONFIG:
+ case DO_FDW:
+ case DO_FOREIGN_SERVER:
+ case DO_BLOB:
+ /* Pre-data objects: must come before the pre-data boundary */
+ addObjectDependency(preDataBound, dobj->dumpId);
+ break;
+ case DO_TABLE_DATA:
+ case DO_BLOB_DATA:
+ /* Data objects: must come between the boundaries */
+ addObjectDependency(dobj, preDataBound->dumpId);
+ addObjectDependency(postDataBound, dobj->dumpId);
+ break;
+ case DO_INDEX:
+ case DO_REFRESH_MATVIEW:
+ case DO_TRIGGER:
+ case DO_EVENT_TRIGGER:
+ case DO_DEFAULT_ACL:
+ /* Post-data objects: must come after the post-data boundary */
+ addObjectDependency(dobj, postDataBound->dumpId);
+ break;
+ case DO_RULE:
+ /* Rules are post-data, but only if dumped separately */
+ if (((RuleInfo *) dobj)->separate)
+ addObjectDependency(dobj, postDataBound->dumpId);
+ break;
+ case DO_CONSTRAINT:
+ case DO_FK_CONSTRAINT:
+ /* Constraints are post-data, but only if dumped separately */
+ if (((ConstraintInfo *) dobj)->separate)
+ addObjectDependency(dobj, postDataBound->dumpId);
+ break;
+ case DO_PRE_DATA_BOUNDARY:
+ /* nothing to do */
+ break;
+ case DO_POST_DATA_BOUNDARY:
+ /* must come after the pre-data boundary */
+ addObjectDependency(dobj, preDataBound->dumpId);
+ break;
+ }
+ }
+}
+
+
+/*
+ * BuildArchiveDependencies - create dependency data for archive TOC entries
+ *
+ * The raw dependency data obtained by getDependencies() is not terribly
+ * useful in an archive dump, because in many cases there are dependency
+ * chains linking through objects that don't appear explicitly in the dump.
+ * For example, a view will depend on its _RETURN rule while the _RETURN rule
+ * will depend on other objects --- but the rule will not appear as a separate
+ * object in the dump. We need to adjust the view's dependencies to include
+ * whatever the rule depends on that is included in the dump.
+ *
+ * Just to make things more complicated, there are also "special" dependencies
+ * such as the dependency of a TABLE DATA item on its TABLE, which we must
+ * not rearrange because pg_restore knows that TABLE DATA only depends on
+ * its table. In these cases we must leave the dependencies strictly as-is
+ * even if they refer to not-to-be-dumped objects.
+ *
+ * To handle this, the convention is that "special" dependencies are created
+ * during ArchiveEntry calls, and an archive TOC item that has any such
+ * entries will not be touched here. Otherwise, we recursively search the
+ * DumpableObject data structures to build the correct dependencies for each
+ * archive TOC item.
+ */
+static void
+BuildArchiveDependencies(Archive *fout)
+{
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
+ TocEntry *te;
+
+ /* Scan all TOC entries in the archive */
+ for (te = AH->toc->next; te != AH->toc; te = te->next)
+ {
+ DumpableObject *dobj;
+ DumpId *dependencies;
+ int nDeps;
+ int allocDeps;
+
+ /* No need to process entries that will not be dumped */
+ if (te->reqs == 0)
+ continue;
+ /* Ignore entries that already have "special" dependencies */
+ if (te->nDeps > 0)
+ continue;
+ /* Otherwise, look up the item's original DumpableObject, if any */
+ dobj = findObjectByDumpId(te->dumpId);
+ if (dobj == NULL)
+ continue;
+ /* No work if it has no dependencies */
+ if (dobj->nDeps <= 0)
+ continue;
+ /* Set up work array */
+ allocDeps = 64;
+ dependencies = (DumpId *) pg_malloc(allocDeps * sizeof(DumpId));
+ nDeps = 0;
+ /* Recursively find all dumpable dependencies */
+ findDumpableDependencies(AH, dobj,
+ &dependencies, &nDeps, &allocDeps);
+ /* And save 'em ... */
+ if (nDeps > 0)
+ {
+ dependencies = (DumpId *) pg_realloc(dependencies,
+ nDeps * sizeof(DumpId));
+ te->dependencies = dependencies;
+ te->nDeps = nDeps;
+ }
+ else
+ free(dependencies);
+ }
+}
+
+/* Recursive search subroutine for BuildArchiveDependencies */
+static void
+findDumpableDependencies(ArchiveHandle *AH, DumpableObject *dobj,
+ DumpId **dependencies, int *nDeps, int *allocDeps)
+{
+ int i;
+
+ /*
+ * Ignore section boundary objects: if we search through them, we'll
+ * report lots of bogus dependencies.
+ */
+ if (dobj->objType == DO_PRE_DATA_BOUNDARY ||
+ dobj->objType == DO_POST_DATA_BOUNDARY)
+ return;
+
+ for (i = 0; i < dobj->nDeps; i++)
+ {
+ DumpId depid = dobj->dependencies[i];
+
+ if (TocIDRequired(AH, depid) != 0)
+ {
+ /* Object will be dumped, so just reference it as a dependency */
+ if (*nDeps >= *allocDeps)
+ {
+ *allocDeps *= 2;
+ *dependencies = (DumpId *) pg_realloc(*dependencies,
+ *allocDeps * sizeof(DumpId));
+ }
+ (*dependencies)[*nDeps] = depid;
+ (*nDeps)++;
+ }
+ else
+ {
+ /*
+ * Object will not be dumped, so recursively consider its deps. We
+ * rely on the assumption that sortDumpableObjects already broke
+ * any dependency loops, else we might recurse infinitely.
+ */
+ DumpableObject *otherdobj = findObjectByDumpId(depid);
+
+ if (otherdobj)
+ findDumpableDependencies(AH, otherdobj,
+ dependencies, nDeps, allocDeps);
+ }
+ }
+}
+
+
+/*
+ * selectSourceSchema - make the specified schema the active search path
+ * in the source database.
+ *
+ * NB: pg_catalog is explicitly searched after the specified schema;
+ * so user names are only qualified if they are cross-schema references,
+ * and system names are only qualified if they conflict with a user name
+ * in the current schema.
+ *
+ * Whenever the selected schema is not pg_catalog, be careful to qualify
+ * references to system catalogs and types in our emitted commands!
+ *
+ * This function is called only from selectSourceSchemaOnAH and
+ * selectSourceSchema.
+ */
+static void
+selectSourceSchema(Archive *fout, const char *schemaName)
+{
+ PQExpBuffer query;
+
+ /* This is checked by the callers already */
+ Assert(schemaName != NULL && *schemaName != '\0');
+
+ /* Not relevant if fetching from pre-7.3 DB */
+ if (fout->remoteVersion < 70300)
+ return;
+
+ query = createPQExpBuffer();
+ appendPQExpBuffer(query, "SET search_path = %s",
+ fmtId(schemaName));
+ if (strcmp(schemaName, "pg_catalog") != 0)
+ appendPQExpBufferStr(query, ", pg_catalog");
+
+ ExecuteSqlStatement(fout, query->data);
+
+ destroyPQExpBuffer(query);
+}
+
+/*
+ * getFormattedTypeName - retrieve a nicely-formatted type name for the
+ * given type name.
+ *
+ * NB: in 7.3 and up the result may depend on the currently-selected
+ * schema; this is why we don't try to cache the names.
+ */
+static char *
+getFormattedTypeName(Archive *fout, Oid oid, OidOptions opts)
+{
+ char *result;
+ PQExpBuffer query;
+ PGresult *res;
+
+ if (oid == 0)
+ {
+ if ((opts & zeroAsOpaque) != 0)
+ return pg_strdup(g_opaque_type);
+ else if ((opts & zeroAsAny) != 0)
+ return pg_strdup("'any'");
+ else if ((opts & zeroAsStar) != 0)
+ return pg_strdup("*");
+ else if ((opts & zeroAsNone) != 0)
+ return pg_strdup("NONE");
+ }
+
+ query = createPQExpBuffer();
+ if (fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT pg_catalog.format_type('%u'::pg_catalog.oid, NULL)",
+ oid);
+ }
+ else if (fout->remoteVersion >= 70100)
+ {
+ appendPQExpBuffer(query, "SELECT format_type('%u'::oid, NULL)",
+ oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT typname "
+ "FROM pg_type "
+ "WHERE oid = '%u'::oid",
+ oid);
+ }
+
+ res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+ if (fout->remoteVersion >= 70100)
+ {
+ /* already quoted */
+ result = pg_strdup(PQgetvalue(res, 0, 0));
+ }
+ else
+ {
+ /* may need to quote it */
+ result = pg_strdup(fmtId(PQgetvalue(res, 0, 0)));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ return result;
+}
+
+/*
+ * myFormatType --- local implementation of format_type for use with 7.0.
+ */
+static char *
+myFormatType(const char *typname, int32 typmod)
+{
+ char *result;
+ bool isarray = false;
+ PQExpBuffer buf = createPQExpBuffer();
+
+ /* Handle array types */
+ if (typname[0] == '_')
+ {
+ isarray = true;
+ typname++;
+ }
+
+ /* Show lengths on bpchar and varchar */
+ if (strcmp(typname, "bpchar") == 0)
+ {
+ int len = (typmod - VARHDRSZ);
+
+ appendPQExpBufferStr(buf, "character");
+ if (len > 1)
+ appendPQExpBuffer(buf, "(%d)",
+ typmod - VARHDRSZ);
+ }
+ else if (strcmp(typname, "varchar") == 0)
+ {
+ appendPQExpBufferStr(buf, "character varying");
+ if (typmod != -1)
+ appendPQExpBuffer(buf, "(%d)",
+ typmod - VARHDRSZ);
+ }
+ else if (strcmp(typname, "numeric") == 0)
+ {
+ appendPQExpBufferStr(buf, "numeric");
+ if (typmod != -1)
+ {
+ int32 tmp_typmod;
+ int precision;
+ int scale;
+
+ tmp_typmod = typmod - VARHDRSZ;
+ precision = (tmp_typmod >> 16) & 0xffff;
+ scale = tmp_typmod & 0xffff;
+ appendPQExpBuffer(buf, "(%d,%d)",
+ precision, scale);
+ }
+ }
+
+ /*
+ * char is an internal single-byte data type; Let's make sure we force it
+ * through with quotes. - thomas 1998-12-13
+ */
+ else if (strcmp(typname, "char") == 0)
+ appendPQExpBufferStr(buf, "\"char\"");
+ else
+ appendPQExpBufferStr(buf, fmtId(typname));
+
+ /* Append array qualifier for array types */
+ if (isarray)
+ appendPQExpBufferStr(buf, "[]");
+
+ result = pg_strdup(buf->data);
+ destroyPQExpBuffer(buf);
+
+ return result;
+}
+
+/*
+ * Return a column list clause for the given relation.
+ *
+ * Special case: if there are no undropped columns in the relation, return
+ * "", not an invalid "()" column list.
+ */
+static const char *
+fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer)
+{
+ int numatts = ti->numatts;
+ char **attnames = ti->attnames;
+ bool *attisdropped = ti->attisdropped;
+ bool needComma;
+ int i;
+
+ appendPQExpBufferChar(buffer, '(');
+ needComma = false;
+ for (i = 0; i < numatts; i++)
+ {
+ if (attisdropped[i])
+ continue;
+ if (needComma)
+ appendPQExpBufferStr(buffer, ", ");
+ appendPQExpBufferStr(buffer, fmtId(attnames[i]));
+ needComma = true;
+ }
+
+ if (!needComma)
+ return ""; /* no undropped columns */
+
+ appendPQExpBufferChar(buffer, ')');
+ return buffer->data;
+}
+
+/*
+ * Execute an SQL query and verify that we got exactly one row back.
+ */
+static PGresult *
+ExecuteSqlQueryForSingleRow(Archive *fout, char *query)
+{
+ PGresult *res;
+ int ntups;
+
+ res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK);
+
+ /* Expecting a single result only */
+ ntups = PQntuples(res);
+ if (ntups != 1)
+ exit_horribly(NULL,
+ ngettext("query returned %d row instead of one: %s\n",
+ "query returned %d rows instead of one: %s\n",
+ ntups),
+ ntups, query);
+
+ return res;
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_dump.h
@@ -0,0 +1,580 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_dump.h
+ * Common header file for the pg_dump utility
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg_dump/pg_dump.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PG_DUMP_H
+#define PG_DUMP_H
+
+#include "postgres_fe.h"
+
+/*
+ * pg_dump uses two different mechanisms for identifying database objects:
+ *
+ * CatalogId represents an object by the tableoid and oid of its defining
+ * entry in the system catalogs. We need this to interpret pg_depend entries,
+ * for instance.
+ *
+ * DumpId is a simple sequential integer counter assigned as dumpable objects
+ * are identified during a pg_dump run. We use DumpId internally in preference
+ * to CatalogId for two reasons: it's more compact, and we can assign DumpIds
+ * to "objects" that don't have a separate CatalogId. For example, it is
+ * convenient to consider a table, its data, and its ACL as three separate
+ * dumpable "objects" with distinct DumpIds --- this lets us reason about the
+ * order in which to dump these things.
+ */
+
+typedef struct
+{
+ Oid tableoid;
+ Oid oid;
+} CatalogId;
+
+typedef int DumpId;
+
+/*
+ * Data structures for simple lists of OIDs and strings. The support for
+ * these is very primitive compared to the backend's List facilities, but
+ * it's all we need in pg_dump.
+ */
+
+typedef struct SimpleOidListCell
+{
+ struct SimpleOidListCell *next;
+ Oid val;
+} SimpleOidListCell;
+
+typedef struct SimpleOidList
+{
+ SimpleOidListCell *head;
+ SimpleOidListCell *tail;
+} SimpleOidList;
+
+
+/*
+ * The data structures used to store system catalog information. Every
+ * dumpable object is a subclass of DumpableObject.
+ *
+ * NOTE: the structures described here live for the entire pg_dump run;
+ * and in most cases we make a struct for every object we can find in the
+ * catalogs, not only those we are actually going to dump. Hence, it's
+ * best to store a minimal amount of per-object info in these structs,
+ * and retrieve additional per-object info when and if we dump a specific
+ * object. In particular, try to avoid retrieving expensive-to-compute
+ * information until it's known to be needed. We do, however, have to
+ * store enough info to determine whether an object should be dumped and
+ * what order to dump in.
+ */
+
+typedef enum
+{
+ /* When modifying this enum, update priority tables in pg_dump_sort.c! */
+ DO_NAMESPACE,
+ DO_EXTENSION,
+ DO_TYPE,
+ DO_SHELL_TYPE,
+ DO_FUNC,
+ DO_AGG,
+ DO_OPERATOR,
+ DO_OPCLASS,
+ DO_OPFAMILY,
+ DO_COLLATION,
+ DO_CONVERSION,
+ DO_TABLE,
+ DO_ATTRDEF,
+ DO_INDEX,
+ DO_RULE,
+ DO_TRIGGER,
+ DO_CONSTRAINT,
+ DO_FK_CONSTRAINT, /* see note for ConstraintInfo */
+ DO_PROCLANG,
+ DO_CAST,
+ DO_TABLE_DATA,
+ DO_DUMMY_TYPE,
+ DO_TSPARSER,
+ DO_TSDICT,
+ DO_TSTEMPLATE,
+ DO_TSCONFIG,
+ DO_FDW,
+ DO_FOREIGN_SERVER,
+ DO_DEFAULT_ACL,
+ DO_BLOB,
+ DO_BLOB_DATA,
+ DO_PRE_DATA_BOUNDARY,
+ DO_POST_DATA_BOUNDARY,
+ DO_EVENT_TRIGGER,
+ DO_REFRESH_MATVIEW
+} DumpableObjectType;
+
+typedef struct _dumpableObject
+{
+ DumpableObjectType objType;
+ CatalogId catId; /* zero if not a cataloged object */
+ DumpId dumpId; /* assigned by AssignDumpId() */
+ char *name; /* object name (should never be NULL) */
+ struct _namespaceInfo *namespace; /* containing namespace, or NULL */
+ bool dump; /* true if we want to dump this object */
+ bool ext_member; /* true if object is member of extension */
+ DumpId *dependencies; /* dumpIds of objects this one depends on */
+ int nDeps; /* number of valid dependencies */
+ int allocDeps; /* allocated size of dependencies[] */
+} DumpableObject;
+
+typedef struct _namespaceInfo
+{
+ DumpableObject dobj;
+ char *rolname; /* name of owner, or empty string */
+ char *nspacl;
+} NamespaceInfo;
+
+typedef struct _extensionInfo
+{
+ DumpableObject dobj;
+ char *namespace; /* schema containing extension's objects */
+ bool relocatable;
+ char *extversion;
+ char *extconfig; /* info about configuration tables */
+ char *extcondition;
+} ExtensionInfo;
+
+typedef struct _typeInfo
+{
+ DumpableObject dobj;
+
+ /*
+ * Note: dobj.name is the pg_type.typname entry. format_type() might
+ * produce something different than typname
+ */
+ char *rolname; /* name of owner, or empty string */
+ char *typacl;
+ Oid typelem;
+ Oid typrelid;
+ char typrelkind; /* 'r', 'v', 'c', etc */
+ char typtype; /* 'b', 'c', etc */
+ bool isArray; /* true if auto-generated array type */
+ bool isDefined; /* true if typisdefined */
+ /* If needed, we'll create a "shell type" entry for it; link that here: */
+ struct _shellTypeInfo *shellType; /* shell-type entry, or NULL */
+ /* If it's a domain, we store links to its constraints here: */
+ int nDomChecks;
+ struct _constraintInfo *domChecks;
+} TypeInfo;
+
+typedef struct _shellTypeInfo
+{
+ DumpableObject dobj;
+
+ TypeInfo *baseType; /* back link to associated base type */
+} ShellTypeInfo;
+
+typedef struct _funcInfo
+{
+ DumpableObject dobj;
+ char *rolname; /* name of owner, or empty string */
+ Oid lang;
+ int nargs;
+ Oid *argtypes;
+ Oid prorettype;
+ char *proacl;
+} FuncInfo;
+
+/* AggInfo is a superset of FuncInfo */
+typedef struct _aggInfo
+{
+ FuncInfo aggfn;
+ /* we don't require any other fields at the moment */
+} AggInfo;
+
+typedef struct _oprInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+ char oprkind;
+ Oid oprcode;
+} OprInfo;
+
+typedef struct _opclassInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+} OpclassInfo;
+
+typedef struct _opfamilyInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+} OpfamilyInfo;
+
+typedef struct _collInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+} CollInfo;
+
+typedef struct _convInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+} ConvInfo;
+
+typedef struct _tableInfo
+{
+ /*
+ * These fields are collected for every table in the database.
+ */
+ DumpableObject dobj;
+ char *rolname; /* name of owner, or empty string */
+ char *relacl;
+ char relkind;
+ char relpersistence; /* relation persistence */
+ bool relispopulated; /* relation is populated */
+ char relreplident; /* replica identifier */
+ char *reltablespace; /* relation tablespace */
+ char *reloptions; /* options specified by WITH (...) */
+ char *checkoption; /* WITH CHECK OPTION */
+ char *toast_reloptions; /* WITH options for the TOAST table */
+ bool hasindex; /* does it have any indexes? */
+ bool hasrules; /* does it have any rules? */
+ bool hastriggers; /* does it have any triggers? */
+ bool hasoids; /* does it have OIDs? */
+ uint32 frozenxid; /* table's relfrozenxid */
+ uint32 minmxid; /* table's relminmxid */
+ Oid toast_oid; /* toast table's OID, or 0 if none */
+ uint32 toast_frozenxid; /* toast table's relfrozenxid, if any */
+ uint32 toast_minmxid; /* toast table's relminmxid */
+ int ncheck; /* # of CHECK expressions */
+ char *reloftype; /* underlying type for typed table */
+ /* these two are set only if table is a sequence owned by a column: */
+ Oid owning_tab; /* OID of table owning sequence */
+ int owning_col; /* attr # of column owning sequence */
+ int relpages; /* table's size in pages (from pg_class) */
+
+ bool interesting; /* true if need to collect more data */
+ bool postponed_def; /* matview must be postponed into post-data */
+
+ /*
+ * These fields are computed only if we decide the table is interesting
+ * (it's either a table to dump, or a direct parent of a dumpable table).
+ */
+ int numatts; /* number of attributes */
+ char **attnames; /* the attribute names */
+ char **atttypnames; /* attribute type names */
+ int *atttypmod; /* type-specific type modifiers */
+ int *attstattarget; /* attribute statistics targets */
+ char *attstorage; /* attribute storage scheme */
+ char *typstorage; /* type storage scheme */
+ bool *attisdropped; /* true if attr is dropped; don't dump it */
+ int *attlen; /* attribute length, used by binary_upgrade */
+ char *attalign; /* attribute align, used by binary_upgrade */
+ bool *attislocal; /* true if attr has local definition */
+ char **attoptions; /* per-attribute options */
+ Oid *attcollation; /* per-attribute collation selection */
+ char **attfdwoptions; /* per-attribute fdw options */
+ bool *notnull; /* NOT NULL constraints on attributes */
+ bool *inhNotNull; /* true if NOT NULL is inherited */
+ struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
+ struct _constraintInfo *checkexprs; /* CHECK constraints */
+
+ /*
+ * Stuff computed only for dumpable tables.
+ */
+ int numParents; /* number of (immediate) parent tables */
+ struct _tableInfo **parents; /* TableInfos of immediate parents */
+ struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
+} TableInfo;
+
+typedef struct _attrDefInfo
+{
+ DumpableObject dobj; /* note: dobj.name is name of table */
+ TableInfo *adtable; /* link to table of attribute */
+ int adnum;
+ char *adef_expr; /* decompiled DEFAULT expression */
+ bool separate; /* TRUE if must dump as separate item */
+} AttrDefInfo;
+
+typedef struct _tableDataInfo
+{
+ DumpableObject dobj;
+ TableInfo *tdtable; /* link to table to dump */
+ bool oids; /* include OIDs in data? */
+ char *filtercond; /* WHERE condition to limit rows dumped */
+} TableDataInfo;
+
+typedef struct _indxInfo
+{
+ DumpableObject dobj;
+ TableInfo *indextable; /* link to table the index is for */
+ char *indexdef;
+ char *tablespace; /* tablespace in which index is stored */
+ char *options; /* options specified by WITH (...) */
+ int indnkeys;
+ Oid *indkeys;
+ bool indisclustered;
+ bool indisreplident;
+ /* if there is an associated constraint object, its dumpId: */
+ DumpId indexconstraint;
+ int relpages; /* relpages of the underlying table */
+} IndxInfo;
+
+typedef struct _ruleInfo
+{
+ DumpableObject dobj;
+ TableInfo *ruletable; /* link to table the rule is for */
+ char ev_type;
+ bool is_instead;
+ char ev_enabled;
+ bool separate; /* TRUE if must dump as separate item */
+ /* separate is always true for non-ON SELECT rules */
+ char *reloptions; /* options specified by WITH (...) */
+ /* reloptions is only set if we need to dump the options with the rule */
+} RuleInfo;
+
+typedef struct _triggerInfo
+{
+ DumpableObject dobj;
+ TableInfo *tgtable; /* link to table the trigger is for */
+ char *tgfname;
+ int tgtype;
+ int tgnargs;
+ char *tgargs;
+ bool tgisconstraint;
+ char *tgconstrname;
+ Oid tgconstrrelid;
+ char *tgconstrrelname;
+ char tgenabled;
+ bool tgdeferrable;
+ bool tginitdeferred;
+ char *tgdef;
+} TriggerInfo;
+
+typedef struct _evttriggerInfo
+{
+ DumpableObject dobj;
+ char *evtname;
+ char *evtevent;
+ char *evtowner;
+ char *evttags;
+ char *evtfname;
+ char evtenabled;
+} EventTriggerInfo;
+
+/*
+ * struct ConstraintInfo is used for all constraint types. However we
+ * use a different objType for foreign key constraints, to make it easier
+ * to sort them the way we want.
+ *
+ * Note: condeferrable and condeferred are currently only valid for
+ * unique/primary-key constraints. Otherwise that info is in condef.
+ */
+typedef struct _constraintInfo
+{
+ DumpableObject dobj;
+ TableInfo *contable; /* NULL if domain constraint */
+ TypeInfo *condomain; /* NULL if table constraint */
+ char contype;
+ char *condef; /* definition, if CHECK or FOREIGN KEY */
+ Oid confrelid; /* referenced table, if FOREIGN KEY */
+ DumpId conindex; /* identifies associated index if any */
+ bool condeferrable; /* TRUE if constraint is DEFERRABLE */
+ bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */
+ bool conislocal; /* TRUE if constraint has local definition */
+ bool separate; /* TRUE if must dump as separate item */
+} ConstraintInfo;
+
+typedef struct _procLangInfo
+{
+ DumpableObject dobj;
+ bool lanpltrusted;
+ Oid lanplcallfoid;
+ Oid laninline;
+ Oid lanvalidator;
+ char *lanacl;
+ char *lanowner; /* name of owner, or empty string */
+} ProcLangInfo;
+
+typedef struct _castInfo
+{
+ DumpableObject dobj;
+ Oid castsource;
+ Oid casttarget;
+ Oid castfunc;
+ char castcontext;
+ char castmethod;
+} CastInfo;
+
+/* InhInfo isn't a DumpableObject, just temporary state */
+typedef struct _inhInfo
+{
+ Oid inhrelid; /* OID of a child table */
+ Oid inhparent; /* OID of its parent */
+} InhInfo;
+
+typedef struct _prsInfo
+{
+ DumpableObject dobj;
+ Oid prsstart;
+ Oid prstoken;
+ Oid prsend;
+ Oid prsheadline;
+ Oid prslextype;
+} TSParserInfo;
+
+typedef struct _dictInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+ Oid dicttemplate;
+ char *dictinitoption;
+} TSDictInfo;
+
+typedef struct _tmplInfo
+{
+ DumpableObject dobj;
+ Oid tmplinit;
+ Oid tmpllexize;
+} TSTemplateInfo;
+
+typedef struct _cfgInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+ Oid cfgparser;
+} TSConfigInfo;
+
+typedef struct _fdwInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+ char *fdwhandler;
+ char *fdwvalidator;
+ char *fdwoptions;
+ char *fdwacl;
+} FdwInfo;
+
+typedef struct _foreignServerInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+ Oid srvfdw;
+ char *srvtype;
+ char *srvversion;
+ char *srvacl;
+ char *srvoptions;
+} ForeignServerInfo;
+
+typedef struct _defaultACLInfo
+{
+ DumpableObject dobj;
+ char *defaclrole;
+ char defaclobjtype;
+ char *defaclacl;
+} DefaultACLInfo;
+
+typedef struct _blobInfo
+{
+ DumpableObject dobj;
+ char *rolname;
+ char *blobacl;
+} BlobInfo;
+
+/* global decls */
+extern bool force_quotes; /* double-quotes for identifiers flag */
+extern bool g_verbose; /* verbose flag */
+
+/* placeholders for comment starting and ending delimiters */
+extern char g_comment_start[10];
+extern char g_comment_end[10];
+
+extern char g_opaque_type[10]; /* name for the opaque type */
+
+/*
+ * common utility functions
+ */
+
+struct Archive;
+typedef struct Archive Archive;
+
+extern TableInfo *getSchemaData(Archive *, int *numTablesPtr);
+
+typedef enum _OidOptions
+{
+ zeroAsOpaque = 1,
+ zeroAsAny = 2,
+ zeroAsStar = 4,
+ zeroAsNone = 8
+} OidOptions;
+
+extern void AssignDumpId(DumpableObject *dobj);
+extern DumpId createDumpId(void);
+extern DumpId getMaxDumpId(void);
+extern DumpableObject *findObjectByDumpId(DumpId dumpId);
+extern DumpableObject *findObjectByCatalogId(CatalogId catalogId);
+extern void getDumpableObjects(DumpableObject ***objs, int *numObjs);
+
+extern void addObjectDependency(DumpableObject *dobj, DumpId refId);
+extern void removeObjectDependency(DumpableObject *dobj, DumpId refId);
+
+extern TableInfo *findTableByOid(Oid oid);
+extern TypeInfo *findTypeByOid(Oid oid);
+extern FuncInfo *findFuncByOid(Oid oid);
+extern OprInfo *findOprByOid(Oid oid);
+extern CollInfo *findCollationByOid(Oid oid);
+extern NamespaceInfo *findNamespaceByOid(Oid oid);
+
+extern void simple_oid_list_append(SimpleOidList *list, Oid val);
+extern bool simple_oid_list_member(SimpleOidList *list, Oid val);
+
+extern void parseOidArray(const char *str, Oid *array, int arraysize);
+
+extern void sortDumpableObjects(DumpableObject **objs, int numObjs,
+ DumpId preBoundaryId, DumpId postBoundaryId);
+extern void sortDumpableObjectsByTypeName(DumpableObject **objs, int numObjs);
+extern void sortDumpableObjectsByTypeOid(DumpableObject **objs, int numObjs);
+extern void sortDataAndIndexObjectsBySize(DumpableObject **objs, int numObjs);
+
+/*
+ * version specific routines
+ */
+extern NamespaceInfo *getNamespaces(Archive *fout, int *numNamespaces);
+extern ExtensionInfo *getExtensions(Archive *fout, int *numExtensions);
+extern TypeInfo *getTypes(Archive *fout, int *numTypes);
+extern FuncInfo *getFuncs(Archive *fout, int *numFuncs);
+extern AggInfo *getAggregates(Archive *fout, int *numAggregates);
+extern OprInfo *getOperators(Archive *fout, int *numOperators);
+extern OpclassInfo *getOpclasses(Archive *fout, int *numOpclasses);
+extern OpfamilyInfo *getOpfamilies(Archive *fout, int *numOpfamilies);
+extern CollInfo *getCollations(Archive *fout, int *numCollations);
+extern ConvInfo *getConversions(Archive *fout, int *numConversions);
+extern TableInfo *getTables(Archive *fout, int *numTables);
+extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
+extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
+extern RuleInfo *getRules(Archive *fout, int *numRules);
+extern void getTriggers(Archive *fout, TableInfo tblinfo[], int numTables);
+extern ProcLangInfo *getProcLangs(Archive *fout, int *numProcLangs);
+extern CastInfo *getCasts(Archive *fout, int *numCasts);
+extern void getTableAttrs(Archive *fout, TableInfo *tbinfo, int numTables);
+extern bool shouldPrintColumn(TableInfo *tbinfo, int colno);
+extern TSParserInfo *getTSParsers(Archive *fout, int *numTSParsers);
+extern TSDictInfo *getTSDictionaries(Archive *fout, int *numTSDicts);
+extern TSTemplateInfo *getTSTemplates(Archive *fout, int *numTSTemplates);
+extern TSConfigInfo *getTSConfigurations(Archive *fout, int *numTSConfigs);
+extern FdwInfo *getForeignDataWrappers(Archive *fout,
+ int *numForeignDataWrappers);
+extern ForeignServerInfo *getForeignServers(Archive *fout,
+ int *numForeignServers);
+extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
+extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
+ int numExtensions);
+extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
+
+#endif /* PG_DUMP_H */
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pg_dump_sort.c
@@ -0,0 +1,1470 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_dump_sort.c
+ * Sort the items of a dump into a safe order for dumping
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/bin/pg_dump/pg_dump_sort.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "pg_backup_archiver.h"
+#include "pg_backup_utils.h"
+#include "parallel.h"
+
+/* translator: this is a module name */
+static const char *modulename = gettext_noop("sorter");
+
+/*
+ * Sort priority for object types when dumping a pre-7.3 database.
+ * Objects are sorted by priority levels, and within an equal priority level
+ * by OID. (This is a relatively crude hack to provide semi-reasonable
+ * behavior for old databases without full dependency info.) Note: collations,
+ * extensions, text search, foreign-data, materialized view, event trigger,
+ * and default ACL objects can't really happen here, so the rather bogus
+ * priorities for them don't matter.
+ *
+ * NOTE: object-type priorities must match the section assignments made in
+ * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY,
+ * POST_DATA objects must sort after DO_POST_DATA_BOUNDARY, and DATA objects
+ * must sort between them.
+ */
+static const int oldObjectTypePriority[] =
+{
+ 1, /* DO_NAMESPACE */
+ 1, /* DO_EXTENSION */
+ 2, /* DO_TYPE */
+ 2, /* DO_SHELL_TYPE */
+ 2, /* DO_FUNC */
+ 3, /* DO_AGG */
+ 3, /* DO_OPERATOR */
+ 4, /* DO_OPCLASS */
+ 4, /* DO_OPFAMILY */
+ 4, /* DO_COLLATION */
+ 5, /* DO_CONVERSION */
+ 6, /* DO_TABLE */
+ 8, /* DO_ATTRDEF */
+ 15, /* DO_INDEX */
+ 16, /* DO_RULE */
+ 17, /* DO_TRIGGER */
+ 14, /* DO_CONSTRAINT */
+ 18, /* DO_FK_CONSTRAINT */
+ 2, /* DO_PROCLANG */
+ 2, /* DO_CAST */
+ 11, /* DO_TABLE_DATA */
+ 7, /* DO_DUMMY_TYPE */
+ 4, /* DO_TSPARSER */
+ 4, /* DO_TSDICT */
+ 4, /* DO_TSTEMPLATE */
+ 4, /* DO_TSCONFIG */
+ 4, /* DO_FDW */
+ 4, /* DO_FOREIGN_SERVER */
+ 19, /* DO_DEFAULT_ACL */
+ 9, /* DO_BLOB */
+ 12, /* DO_BLOB_DATA */
+ 10, /* DO_PRE_DATA_BOUNDARY */
+ 13, /* DO_POST_DATA_BOUNDARY */
+ 20, /* DO_EVENT_TRIGGER */
+ 15 /* DO_REFRESH_MATVIEW */
+};
+
+/*
+ * Sort priority for object types when dumping newer databases.
+ * Objects are sorted by type, and within a type by name.
+ *
+ * NOTE: object-type priorities must match the section assignments made in
+ * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY,
+ * POST_DATA objects must sort after DO_POST_DATA_BOUNDARY, and DATA objects
+ * must sort between them.
+ */
+static const int newObjectTypePriority[] =
+{
+ 1, /* DO_NAMESPACE */
+ 4, /* DO_EXTENSION */
+ 5, /* DO_TYPE */
+ 5, /* DO_SHELL_TYPE */
+ 6, /* DO_FUNC */
+ 7, /* DO_AGG */
+ 8, /* DO_OPERATOR */
+ 9, /* DO_OPCLASS */
+ 9, /* DO_OPFAMILY */
+ 3, /* DO_COLLATION */
+ 11, /* DO_CONVERSION */
+ 18, /* DO_TABLE */
+ 20, /* DO_ATTRDEF */
+ 27, /* DO_INDEX */
+ 28, /* DO_RULE */
+ 29, /* DO_TRIGGER */
+ 26, /* DO_CONSTRAINT */
+ 30, /* DO_FK_CONSTRAINT */
+ 2, /* DO_PROCLANG */
+ 10, /* DO_CAST */
+ 23, /* DO_TABLE_DATA */
+ 19, /* DO_DUMMY_TYPE */
+ 12, /* DO_TSPARSER */
+ 14, /* DO_TSDICT */
+ 13, /* DO_TSTEMPLATE */
+ 15, /* DO_TSCONFIG */
+ 16, /* DO_FDW */
+ 17, /* DO_FOREIGN_SERVER */
+ 31, /* DO_DEFAULT_ACL */
+ 21, /* DO_BLOB */
+ 24, /* DO_BLOB_DATA */
+ 22, /* DO_PRE_DATA_BOUNDARY */
+ 25, /* DO_POST_DATA_BOUNDARY */
+ 32, /* DO_EVENT_TRIGGER */
+ 33 /* DO_REFRESH_MATVIEW */
+};
+
+static DumpId preDataBoundId;
+static DumpId postDataBoundId;
+
+
+static int DOTypeNameCompare(const void *p1, const void *p2);
+static int DOTypeOidCompare(const void *p1, const void *p2);
+static bool TopoSort(DumpableObject **objs,
+ int numObjs,
+ DumpableObject **ordering,
+ int *nOrdering);
+static void addHeapElement(int val, int *heap, int heapLength);
+static int removeHeapElement(int *heap, int heapLength);
+static void findDependencyLoops(DumpableObject **objs, int nObjs, int totObjs);
+static int findLoop(DumpableObject *obj,
+ DumpId startPoint,
+ bool *processed,
+ DumpId *searchFailed,
+ DumpableObject **workspace,
+ int depth);
+static void repairDependencyLoop(DumpableObject **loop,
+ int nLoop);
+static void describeDumpableObject(DumpableObject *obj,
+ char *buf, int bufsize);
+
+static int DOSizeCompare(const void *p1, const void *p2);
+
+static int
+findFirstEqualType(DumpableObjectType type, DumpableObject **objs, int numObjs)
+{
+ int i;
+
+ for (i = 0; i < numObjs; i++)
+ if (objs[i]->objType == type)
+ return i;
+ return -1;
+}
+
+static int
+findFirstDifferentType(DumpableObjectType type, DumpableObject **objs, int numObjs, int start)
+{
+ int i;
+
+ for (i = start; i < numObjs; i++)
+ if (objs[i]->objType != type)
+ return i;
+ return numObjs - 1;
+}
+
+/*
+ * When we do a parallel dump, we want to start with the largest items first.
+ *
+ * Say we have the objects in this order:
+ * ....DDDDD....III....
+ *
+ * with D = Table data, I = Index, . = other object
+ *
+ * This sorting function now takes each of the D or I blocks and sorts them
+ * according to their size.
+ */
+void
+sortDataAndIndexObjectsBySize(DumpableObject **objs, int numObjs)
+{
+ int startIdx,
+ endIdx;
+ void *startPtr;
+
+ if (numObjs <= 1)
+ return;
+
+ startIdx = findFirstEqualType(DO_TABLE_DATA, objs, numObjs);
+ if (startIdx >= 0)
+ {
+ endIdx = findFirstDifferentType(DO_TABLE_DATA, objs, numObjs, startIdx);
+ startPtr = objs + startIdx;
+ qsort(startPtr, endIdx - startIdx, sizeof(DumpableObject *),
+ DOSizeCompare);
+ }
+
+ startIdx = findFirstEqualType(DO_INDEX, objs, numObjs);
+ if (startIdx >= 0)
+ {
+ endIdx = findFirstDifferentType(DO_INDEX, objs, numObjs, startIdx);
+ startPtr = objs + startIdx;
+ qsort(startPtr, endIdx - startIdx, sizeof(DumpableObject *),
+ DOSizeCompare);
+ }
+}
+
+static int
+DOSizeCompare(const void *p1, const void *p2)
+{
+ DumpableObject *obj1 = *(DumpableObject **) p1;
+ DumpableObject *obj2 = *(DumpableObject **) p2;
+ int obj1_size = 0;
+ int obj2_size = 0;
+
+ if (obj1->objType == DO_TABLE_DATA)
+ obj1_size = ((TableDataInfo *) obj1)->tdtable->relpages;
+ if (obj1->objType == DO_INDEX)
+ obj1_size = ((IndxInfo *) obj1)->relpages;
+
+ if (obj2->objType == DO_TABLE_DATA)
+ obj2_size = ((TableDataInfo *) obj2)->tdtable->relpages;
+ if (obj2->objType == DO_INDEX)
+ obj2_size = ((IndxInfo *) obj2)->relpages;
+
+ /* we want to see the biggest item go first */
+ if (obj1_size > obj2_size)
+ return -1;
+ if (obj2_size > obj1_size)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Sort the given objects into a type/name-based ordering
+ *
+ * Normally this is just the starting point for the dependency-based
+ * ordering.
+ */
+void
+sortDumpableObjectsByTypeName(DumpableObject **objs, int numObjs)
+{
+ if (numObjs > 1)
+ qsort((void *) objs, numObjs, sizeof(DumpableObject *),
+ DOTypeNameCompare);
+}
+
+static int
+DOTypeNameCompare(const void *p1, const void *p2)
+{
+ DumpableObject *obj1 = *(DumpableObject *const *) p1;
+ DumpableObject *obj2 = *(DumpableObject *const *) p2;
+ int cmpval;
+
+ /* Sort by type */
+ cmpval = newObjectTypePriority[obj1->objType] -
+ newObjectTypePriority[obj2->objType];
+
+ if (cmpval != 0)
+ return cmpval;
+
+ /*
+ * Sort by namespace. Note that all objects of the same type should
+ * either have or not have a namespace link, so we needn't be fancy about
+ * cases where one link is null and the other not.
+ */
+ if (obj1->namespace && obj2->namespace)
+ {
+ cmpval = strcmp(obj1->namespace->dobj.name,
+ obj2->namespace->dobj.name);
+ if (cmpval != 0)
+ return cmpval;
+ }
+
+ /* Sort by name */
+ cmpval = strcmp(obj1->name, obj2->name);
+ if (cmpval != 0)
+ return cmpval;
+
+ /* To have a stable sort order, break ties for some object types */
+ if (obj1->objType == DO_FUNC || obj1->objType == DO_AGG)
+ {
+ FuncInfo *fobj1 = *(FuncInfo *const *) p1;
+ FuncInfo *fobj2 = *(FuncInfo *const *) p2;
+ int i;
+
+ cmpval = fobj1->nargs - fobj2->nargs;
+ if (cmpval != 0)
+ return cmpval;
+ for (i = 0; i < fobj1->nargs; i++)
+ {
+ TypeInfo *argtype1 = findTypeByOid(fobj1->argtypes[i]);
+ TypeInfo *argtype2 = findTypeByOid(fobj2->argtypes[i]);
+
+ if (argtype1 && argtype2)
+ {
+ if (argtype1->dobj.namespace && argtype2->dobj.namespace)
+ {
+ cmpval = strcmp(argtype1->dobj.namespace->dobj.name,
+ argtype2->dobj.namespace->dobj.name);
+ if (cmpval != 0)
+ return cmpval;
+ }
+ cmpval = strcmp(argtype1->dobj.name, argtype2->dobj.name);
+ if (cmpval != 0)
+ return cmpval;
+ }
+ }
+ }
+ else if (obj1->objType == DO_OPERATOR)
+ {
+ OprInfo *oobj1 = *(OprInfo *const *) p1;
+ OprInfo *oobj2 = *(OprInfo *const *) p2;
+
+ /* oprkind is 'l', 'r', or 'b'; this sorts prefix, postfix, infix */
+ cmpval = (oobj2->oprkind - oobj1->oprkind);
+ if (cmpval != 0)
+ return cmpval;
+ }
+ else if (obj1->objType == DO_ATTRDEF)
+ {
+ AttrDefInfo *adobj1 = *(AttrDefInfo *const *) p1;
+ AttrDefInfo *adobj2 = *(AttrDefInfo *const *) p2;
+
+ cmpval = (adobj1->adnum - adobj2->adnum);
+ if (cmpval != 0)
+ return cmpval;
+ }
+
+ /* Usually shouldn't get here, but if we do, sort by OID */
+ return oidcmp(obj1->catId.oid, obj2->catId.oid);
+}
+
+
+/*
+ * Sort the given objects into a type/OID-based ordering
+ *
+ * This is used with pre-7.3 source databases as a crude substitute for the
+ * lack of dependency information.
+ */
+void
+sortDumpableObjectsByTypeOid(DumpableObject **objs, int numObjs)
+{
+ if (numObjs > 1)
+ qsort((void *) objs, numObjs, sizeof(DumpableObject *),
+ DOTypeOidCompare);
+}
+
+static int
+DOTypeOidCompare(const void *p1, const void *p2)
+{
+ DumpableObject *obj1 = *(DumpableObject *const *) p1;
+ DumpableObject *obj2 = *(DumpableObject *const *) p2;
+ int cmpval;
+
+ cmpval = oldObjectTypePriority[obj1->objType] -
+ oldObjectTypePriority[obj2->objType];
+
+ if (cmpval != 0)
+ return cmpval;
+
+ return oidcmp(obj1->catId.oid, obj2->catId.oid);
+}
+
+
+/*
+ * Sort the given objects into a safe dump order using dependency
+ * information (to the extent we have it available).
+ *
+ * The DumpIds of the PRE_DATA_BOUNDARY and POST_DATA_BOUNDARY objects are
+ * passed in separately, in case we need them during dependency loop repair.
+ */
+void
+sortDumpableObjects(DumpableObject **objs, int numObjs,
+ DumpId preBoundaryId, DumpId postBoundaryId)
+{
+ DumpableObject **ordering;
+ int nOrdering;
+
+ if (numObjs <= 0) /* can't happen anymore ... */
+ return;
+
+ /*
+ * Saving the boundary IDs in static variables is a bit grotty, but seems
+ * better than adding them to parameter lists of subsidiary functions.
+ */
+ preDataBoundId = preBoundaryId;
+ postDataBoundId = postBoundaryId;
+
+ ordering = (DumpableObject **) pg_malloc(numObjs * sizeof(DumpableObject *));
+ while (!TopoSort(objs, numObjs, ordering, &nOrdering))
+ findDependencyLoops(ordering, nOrdering, numObjs);
+
+ memcpy(objs, ordering, numObjs * sizeof(DumpableObject *));
+
+ free(ordering);
+}
+
+/*
+ * TopoSort -- topological sort of a dump list
+ *
+ * Generate a re-ordering of the dump list that satisfies all the dependency
+ * constraints shown in the dump list. (Each such constraint is a fact of a
+ * partial ordering.) Minimize rearrangement of the list not needed to
+ * achieve the partial ordering.
+ *
+ * The input is the list of numObjs objects in objs[]. This list is not
+ * modified.
+ *
+ * Returns TRUE if able to build an ordering that satisfies all the
+ * constraints, FALSE if not (there are contradictory constraints).
+ *
+ * On success (TRUE result), ordering[] is filled with a sorted array of
+ * DumpableObject pointers, of length equal to the input list length.
+ *
+ * On failure (FALSE result), ordering[] is filled with an unsorted array of
+ * DumpableObject pointers of length *nOrdering, listing the objects that
+ * prevented the sort from being completed. In general, these objects either
+ * participate directly in a dependency cycle, or are depended on by objects
+ * that are in a cycle. (The latter objects are not actually problematic,
+ * but it takes further analysis to identify which are which.)
+ *
+ * The caller is responsible for allocating sufficient space at *ordering.
+ */
+static bool
+TopoSort(DumpableObject **objs,
+ int numObjs,
+ DumpableObject **ordering, /* output argument */
+ int *nOrdering) /* output argument */
+{
+ DumpId maxDumpId = getMaxDumpId();
+ int *pendingHeap;
+ int *beforeConstraints;
+ int *idMap;
+ DumpableObject *obj;
+ int heapLength;
+ int i,
+ j,
+ k;
+
+ /*
+ * This is basically the same algorithm shown for topological sorting in
+ * Knuth's Volume 1. However, we would like to minimize unnecessary
+ * rearrangement of the input ordering; that is, when we have a choice of
+ * which item to output next, we always want to take the one highest in
+ * the original list. Therefore, instead of maintaining an unordered
+ * linked list of items-ready-to-output as Knuth does, we maintain a heap
+ * of their item numbers, which we can use as a priority queue. This
+ * turns the algorithm from O(N) to O(N log N) because each insertion or
+ * removal of a heap item takes O(log N) time. However, that's still
+ * plenty fast enough for this application.
+ */
+
+ *nOrdering = numObjs; /* for success return */
+
+ /* Eliminate the null case */
+ if (numObjs <= 0)
+ return true;
+
+ /* Create workspace for the above-described heap */
+ pendingHeap = (int *) pg_malloc(numObjs * sizeof(int));
+
+ /*
+ * Scan the constraints, and for each item in the input, generate a count
+ * of the number of constraints that say it must be before something else.
+ * The count for the item with dumpId j is stored in beforeConstraints[j].
+ * We also make a map showing the input-order index of the item with
+ * dumpId j.
+ */
+ beforeConstraints = (int *) pg_malloc((maxDumpId + 1) * sizeof(int));
+ memset(beforeConstraints, 0, (maxDumpId + 1) * sizeof(int));
+ idMap = (int *) pg_malloc((maxDumpId + 1) * sizeof(int));
+ for (i = 0; i < numObjs; i++)
+ {
+ obj = objs[i];
+ j = obj->dumpId;
+ if (j <= 0 || j > maxDumpId)
+ exit_horribly(modulename, "invalid dumpId %d\n", j);
+ idMap[j] = i;
+ for (j = 0; j < obj->nDeps; j++)
+ {
+ k = obj->dependencies[j];
+ if (k <= 0 || k > maxDumpId)
+ exit_horribly(modulename, "invalid dependency %d\n", k);
+ beforeConstraints[k]++;
+ }
+ }
+
+ /*
+ * Now initialize the heap of items-ready-to-output by filling it with the
+ * indexes of items that already have beforeConstraints[id] == 0.
+ *
+ * The essential property of a heap is heap[(j-1)/2] >= heap[j] for each j
+ * in the range 1..heapLength-1 (note we are using 0-based subscripts
+ * here, while the discussion in Knuth assumes 1-based subscripts). So, if
+ * we simply enter the indexes into pendingHeap[] in decreasing order, we
+ * a-fortiori have the heap invariant satisfied at completion of this
+ * loop, and don't need to do any sift-up comparisons.
+ */
+ heapLength = 0;
+ for (i = numObjs; --i >= 0;)
+ {
+ if (beforeConstraints[objs[i]->dumpId] == 0)
+ pendingHeap[heapLength++] = i;
+ }
+
+ /*--------------------
+ * Now emit objects, working backwards in the output list. At each step,
+ * we use the priority heap to select the last item that has no remaining
+ * before-constraints. We remove that item from the heap, output it to
+ * ordering[], and decrease the beforeConstraints count of each of the
+ * items it was constrained against. Whenever an item's beforeConstraints
+ * count is thereby decreased to zero, we insert it into the priority heap
+ * to show that it is a candidate to output. We are done when the heap
+ * becomes empty; if we have output every element then we succeeded,
+ * otherwise we failed.
+ * i = number of ordering[] entries left to output
+ * j = objs[] index of item we are outputting
+ * k = temp for scanning constraint list for item j
+ *--------------------
+ */
+ i = numObjs;
+ while (heapLength > 0)
+ {
+ /* Select object to output by removing largest heap member */
+ j = removeHeapElement(pendingHeap, heapLength--);
+ obj = objs[j];
+ /* Output candidate to ordering[] */
+ ordering[--i] = obj;
+ /* Update beforeConstraints counts of its predecessors */
+ for (k = 0; k < obj->nDeps; k++)
+ {
+ int id = obj->dependencies[k];
+
+ if ((--beforeConstraints[id]) == 0)
+ addHeapElement(idMap[id], pendingHeap, heapLength++);
+ }
+ }
+
+ /*
+ * If we failed, report the objects that couldn't be output; these are the
+ * ones with beforeConstraints[] still nonzero.
+ */
+ if (i != 0)
+ {
+ k = 0;
+ for (j = 1; j <= maxDumpId; j++)
+ {
+ if (beforeConstraints[j] != 0)
+ ordering[k++] = objs[idMap[j]];
+ }
+ *nOrdering = k;
+ }
+
+ /* Done */
+ free(pendingHeap);
+ free(beforeConstraints);
+ free(idMap);
+
+ return (i == 0);
+}
+
+/*
+ * Add an item to a heap (priority queue)
+ *
+ * heapLength is the current heap size; caller is responsible for increasing
+ * its value after the call. There must be sufficient storage at *heap.
+ */
+static void
+addHeapElement(int val, int *heap, int heapLength)
+{
+ int j;
+
+ /*
+ * Sift-up the new entry, per Knuth 5.2.3 exercise 16. Note that Knuth is
+ * using 1-based array indexes, not 0-based.
+ */
+ j = heapLength;
+ while (j > 0)
+ {
+ int i = (j - 1) >> 1;
+
+ if (val <= heap[i])
+ break;
+ heap[j] = heap[i];
+ j = i;
+ }
+ heap[j] = val;
+}
+
+/*
+ * Remove the largest item present in a heap (priority queue)
+ *
+ * heapLength is the current heap size; caller is responsible for decreasing
+ * its value after the call.
+ *
+ * We remove and return heap[0], which is always the largest element of
+ * the heap, and then "sift up" to maintain the heap invariant.
+ */
+static int
+removeHeapElement(int *heap, int heapLength)
+{
+ int result = heap[0];
+ int val;
+ int i;
+
+ if (--heapLength <= 0)
+ return result;
+ val = heap[heapLength]; /* value that must be reinserted */
+ i = 0; /* i is where the "hole" is */
+ for (;;)
+ {
+ int j = 2 * i + 1;
+
+ if (j >= heapLength)
+ break;
+ if (j + 1 < heapLength &&
+ heap[j] < heap[j + 1])
+ j++;
+ if (val >= heap[j])
+ break;
+ heap[i] = heap[j];
+ i = j;
+ }
+ heap[i] = val;
+ return result;
+}
+
+/*
+ * findDependencyLoops - identify loops in TopoSort's failure output,
+ * and pass each such loop to repairDependencyLoop() for action
+ *
+ * In general there may be many loops in the set of objects returned by
+ * TopoSort; for speed we should try to repair as many loops as we can
+ * before trying TopoSort again. We can safely repair loops that are
+ * disjoint (have no members in common); if we find overlapping loops
+ * then we repair only the first one found, because the action taken to
+ * repair the first might have repaired the other as well. (If not,
+ * we'll fix it on the next go-round.)
+ *
+ * objs[] lists the objects TopoSort couldn't sort
+ * nObjs is the number of such objects
+ * totObjs is the total number of objects in the universe
+ */
+static void
+findDependencyLoops(DumpableObject **objs, int nObjs, int totObjs)
+{
+ /*
+ * We use three data structures here:
+ *
+ * processed[] is a bool array indexed by dump ID, marking the objects
+ * already processed during this invocation of findDependencyLoops().
+ *
+ * searchFailed[] is another array indexed by dump ID. searchFailed[j] is
+ * set to dump ID k if we have proven that there is no dependency path
+ * leading from object j back to start point k. This allows us to skip
+ * useless searching when there are multiple dependency paths from k to j,
+ * which is a common situation. We could use a simple bool array for
+ * this, but then we'd need to re-zero it for each start point, resulting
+ * in O(N^2) zeroing work. Using the start point's dump ID as the "true"
+ * value lets us skip clearing the array before we consider the next start
+ * point.
+ *
+ * workspace[] is an array of DumpableObject pointers, in which we try to
+ * build lists of objects constituting loops. We make workspace[] large
+ * enough to hold all the objects in TopoSort's output, which is huge
+ * overkill in most cases but could theoretically be necessary if there is
+ * a single dependency chain linking all the objects.
+ */
+ bool *processed;
+ DumpId *searchFailed;
+ DumpableObject **workspace;
+ bool fixedloop;
+ int i;
+
+ processed = (bool *) pg_malloc0((getMaxDumpId() + 1) * sizeof(bool));
+ searchFailed = (DumpId *) pg_malloc0((getMaxDumpId() + 1) * sizeof(DumpId));
+ workspace = (DumpableObject **) pg_malloc(totObjs * sizeof(DumpableObject *));
+ fixedloop = false;
+
+ for (i = 0; i < nObjs; i++)
+ {
+ DumpableObject *obj = objs[i];
+ int looplen;
+ int j;
+
+ looplen = findLoop(obj,
+ obj->dumpId,
+ processed,
+ searchFailed,
+ workspace,
+ 0);
+
+ if (looplen > 0)
+ {
+ /* Found a loop, repair it */
+ repairDependencyLoop(workspace, looplen);
+ fixedloop = true;
+ /* Mark loop members as processed */
+ for (j = 0; j < looplen; j++)
+ processed[workspace[j]->dumpId] = true;
+ }
+ else
+ {
+ /*
+ * There's no loop starting at this object, but mark it processed
+ * anyway. This is not necessary for correctness, but saves later
+ * invocations of findLoop() from uselessly chasing references to
+ * such an object.
+ */
+ processed[obj->dumpId] = true;
+ }
+ }
+
+ /* We'd better have fixed at least one loop */
+ if (!fixedloop)
+ exit_horribly(modulename, "could not identify dependency loop\n");
+
+ free(workspace);
+ free(searchFailed);
+ free(processed);
+}
+
+/*
+ * Recursively search for a circular dependency loop that doesn't include
+ * any already-processed objects.
+ *
+ * obj: object we are examining now
+ * startPoint: dumpId of starting object for the hoped-for circular loop
+ * processed[]: flag array marking already-processed objects
+ * searchFailed[]: flag array marking already-unsuccessfully-visited objects
+ * workspace[]: work array in which we are building list of loop members
+ * depth: number of valid entries in workspace[] at call
+ *
+ * On success, the length of the loop is returned, and workspace[] is filled
+ * with pointers to the members of the loop. On failure, we return 0.
+ *
+ * Note: it is possible that the given starting object is a member of more
+ * than one cycle; if so, we will find an arbitrary one of the cycles.
+ */
+static int
+findLoop(DumpableObject *obj,
+ DumpId startPoint,
+ bool *processed,
+ DumpId *searchFailed,
+ DumpableObject **workspace,
+ int depth)
+{
+ int i;
+
+ /*
+ * Reject if obj is already processed. This test prevents us from finding
+ * loops that overlap previously-processed loops.
+ */
+ if (processed[obj->dumpId])
+ return 0;
+
+ /*
+ * If we've already proven there is no path from this object back to the
+ * startPoint, forget it.
+ */
+ if (searchFailed[obj->dumpId] == startPoint)
+ return 0;
+
+ /*
+ * Reject if obj is already present in workspace. This test prevents us
+ * from going into infinite recursion if we are given a startPoint object
+ * that links to a cycle it's not a member of, and it guarantees that we
+ * can't overflow the allocated size of workspace[].
+ */
+ for (i = 0; i < depth; i++)
+ {
+ if (workspace[i] == obj)
+ return 0;
+ }
+
+ /*
+ * Okay, tentatively add obj to workspace
+ */
+ workspace[depth++] = obj;
+
+ /*
+ * See if we've found a loop back to the desired startPoint; if so, done
+ */
+ for (i = 0; i < obj->nDeps; i++)
+ {
+ if (obj->dependencies[i] == startPoint)
+ return depth;
+ }
+
+ /*
+ * Recurse down each outgoing branch
+ */
+ for (i = 0; i < obj->nDeps; i++)
+ {
+ DumpableObject *nextobj = findObjectByDumpId(obj->dependencies[i]);
+ int newDepth;
+
+ if (!nextobj)
+ continue; /* ignore dependencies on undumped objects */
+ newDepth = findLoop(nextobj,
+ startPoint,
+ processed,
+ searchFailed,
+ workspace,
+ depth);
+ if (newDepth > 0)
+ return newDepth;
+ }
+
+ /*
+ * Remember there is no path from here back to startPoint
+ */
+ searchFailed[obj->dumpId] = startPoint;
+
+ return 0;
+}
+
+/*
+ * A user-defined datatype will have a dependency loop with each of its
+ * I/O functions (since those have the datatype as input or output).
+ * Similarly, a range type will have a loop with its canonicalize function,
+ * if any. Break the loop by making the function depend on the associated
+ * shell type, instead.
+ */
+static void
+repairTypeFuncLoop(DumpableObject *typeobj, DumpableObject *funcobj)
+{
+ TypeInfo *typeInfo = (TypeInfo *) typeobj;
+
+ /* remove function's dependency on type */
+ removeObjectDependency(funcobj, typeobj->dumpId);
+
+ /* add function's dependency on shell type, instead */
+ if (typeInfo->shellType)
+ {
+ addObjectDependency(funcobj, typeInfo->shellType->dobj.dumpId);
+ /* Mark shell type as to be dumped if any such function is */
+ if (funcobj->dump)
+ typeInfo->shellType->dobj.dump = true;
+ }
+}
+
+/*
+ * Because we force a view to depend on its ON SELECT rule, while there
+ * will be an implicit dependency in the other direction, we need to break
+ * the loop. If there are no other objects in the loop then we can remove
+ * the implicit dependency and leave the ON SELECT rule non-separate.
+ * This applies to matviews, as well.
+ */
+static void
+repairViewRuleLoop(DumpableObject *viewobj,
+ DumpableObject *ruleobj)
+{
+ /* remove rule's dependency on view */
+ removeObjectDependency(ruleobj, viewobj->dumpId);
+}
+
+/*
+ * However, if there are other objects in the loop, we must break the loop
+ * by making the ON SELECT rule a separately-dumped object.
+ *
+ * Because findLoop() finds shorter cycles before longer ones, it's likely
+ * that we will have previously fired repairViewRuleLoop() and removed the
+ * rule's dependency on the view. Put it back to ensure the rule won't be
+ * emitted before the view.
+ *
+ * Note: this approach does *not* work for matviews, at the moment.
+ */
+static void
+repairViewRuleMultiLoop(DumpableObject *viewobj,
+ DumpableObject *ruleobj)
+{
+ TableInfo *viewinfo = (TableInfo *) viewobj;
+ RuleInfo *ruleinfo = (RuleInfo *) ruleobj;
+
+ /* remove view's dependency on rule */
+ removeObjectDependency(viewobj, ruleobj->dumpId);
+ /* pretend view is a plain table and dump it that way */
+ viewinfo->relkind = 'r'; /* RELKIND_RELATION */
+ /* mark rule as needing its own dump */
+ ruleinfo->separate = true;
+ /* move any reloptions from view to rule */
+ if (viewinfo->reloptions)
+ {
+ ruleinfo->reloptions = viewinfo->reloptions;
+ viewinfo->reloptions = NULL;
+ }
+ /* put back rule's dependency on view */
+ addObjectDependency(ruleobj, viewobj->dumpId);
+ /* now that rule is separate, it must be post-data */
+ addObjectDependency(ruleobj, postDataBoundId);
+}
+
+/*
+ * If a matview is involved in a multi-object loop, we can't currently fix
+ * that by splitting off the rule. As a stopgap, we try to fix it by
+ * dropping the constraint that the matview be dumped in the pre-data section.
+ * This is sufficient to handle cases where a matview depends on some unique
+ * index, as can happen if it has a GROUP BY for example.
+ *
+ * Note that the "next object" is not necessarily the matview itself;
+ * it could be the matview's rowtype, for example. We may come through here
+ * several times while removing all the pre-data linkages.
+ */
+static void
+repairMatViewBoundaryMultiLoop(DumpableObject *matviewobj,
+ DumpableObject *boundaryobj,
+ DumpableObject *nextobj)
+{
+ TableInfo *matviewinfo = (TableInfo *) matviewobj;
+
+ /* remove boundary's dependency on object after it in loop */
+ removeObjectDependency(boundaryobj, nextobj->dumpId);
+ /* mark matview as postponed into post-data section */
+ matviewinfo->postponed_def = true;
+}
+
+/*
+ * Because we make tables depend on their CHECK constraints, while there
+ * will be an automatic dependency in the other direction, we need to break
+ * the loop. If there are no other objects in the loop then we can remove
+ * the automatic dependency and leave the CHECK constraint non-separate.
+ */
+static void
+repairTableConstraintLoop(DumpableObject *tableobj,
+ DumpableObject *constraintobj)
+{
+ /* remove constraint's dependency on table */
+ removeObjectDependency(constraintobj, tableobj->dumpId);
+}
+
+/*
+ * However, if there are other objects in the loop, we must break the loop
+ * by making the CHECK constraint a separately-dumped object.
+ *
+ * Because findLoop() finds shorter cycles before longer ones, it's likely
+ * that we will have previously fired repairTableConstraintLoop() and
+ * removed the constraint's dependency on the table. Put it back to ensure
+ * the constraint won't be emitted before the table...
+ */
+static void
+repairTableConstraintMultiLoop(DumpableObject *tableobj,
+ DumpableObject *constraintobj)
+{
+ /* remove table's dependency on constraint */
+ removeObjectDependency(tableobj, constraintobj->dumpId);
+ /* mark constraint as needing its own dump */
+ ((ConstraintInfo *) constraintobj)->separate = true;
+ /* put back constraint's dependency on table */
+ addObjectDependency(constraintobj, tableobj->dumpId);
+ /* now that constraint is separate, it must be post-data */
+ addObjectDependency(constraintobj, postDataBoundId);
+}
+
+/*
+ * Attribute defaults behave exactly the same as CHECK constraints...
+ */
+static void
+repairTableAttrDefLoop(DumpableObject *tableobj,
+ DumpableObject *attrdefobj)
+{
+ /* remove attrdef's dependency on table */
+ removeObjectDependency(attrdefobj, tableobj->dumpId);
+}
+
+static void
+repairTableAttrDefMultiLoop(DumpableObject *tableobj,
+ DumpableObject *attrdefobj)
+{
+ /* remove table's dependency on attrdef */
+ removeObjectDependency(tableobj, attrdefobj->dumpId);
+ /* mark attrdef as needing its own dump */
+ ((AttrDefInfo *) attrdefobj)->separate = true;
+ /* put back attrdef's dependency on table */
+ addObjectDependency(attrdefobj, tableobj->dumpId);
+}
+
+/*
+ * CHECK constraints on domains work just like those on tables ...
+ */
+static void
+repairDomainConstraintLoop(DumpableObject *domainobj,
+ DumpableObject *constraintobj)
+{
+ /* remove constraint's dependency on domain */
+ removeObjectDependency(constraintobj, domainobj->dumpId);
+}
+
+static void
+repairDomainConstraintMultiLoop(DumpableObject *domainobj,
+ DumpableObject *constraintobj)
+{
+ /* remove domain's dependency on constraint */
+ removeObjectDependency(domainobj, constraintobj->dumpId);
+ /* mark constraint as needing its own dump */
+ ((ConstraintInfo *) constraintobj)->separate = true;
+ /* put back constraint's dependency on domain */
+ addObjectDependency(constraintobj, domainobj->dumpId);
+ /* now that constraint is separate, it must be post-data */
+ addObjectDependency(constraintobj, postDataBoundId);
+}
+
+/*
+ * Fix a dependency loop, or die trying ...
+ *
+ * This routine is mainly concerned with reducing the multiple ways that
+ * a loop might appear to common cases, which it passes off to the
+ * "fixer" routines above.
+ */
+static void
+repairDependencyLoop(DumpableObject **loop,
+ int nLoop)
+{
+ int i,
+ j;
+
+ /* Datatype and one of its I/O or canonicalize functions */
+ if (nLoop == 2 &&
+ loop[0]->objType == DO_TYPE &&
+ loop[1]->objType == DO_FUNC)
+ {
+ repairTypeFuncLoop(loop[0], loop[1]);
+ return;
+ }
+ if (nLoop == 2 &&
+ loop[1]->objType == DO_TYPE &&
+ loop[0]->objType == DO_FUNC)
+ {
+ repairTypeFuncLoop(loop[1], loop[0]);
+ return;
+ }
+
+ /* View (including matview) and its ON SELECT rule */
+ if (nLoop == 2 &&
+ loop[0]->objType == DO_TABLE &&
+ loop[1]->objType == DO_RULE &&
+ (((TableInfo *) loop[0])->relkind == 'v' || /* RELKIND_VIEW */
+ ((TableInfo *) loop[0])->relkind == 'm') && /* RELKIND_MATVIEW */
+ ((RuleInfo *) loop[1])->ev_type == '1' &&
+ ((RuleInfo *) loop[1])->is_instead &&
+ ((RuleInfo *) loop[1])->ruletable == (TableInfo *) loop[0])
+ {
+ repairViewRuleLoop(loop[0], loop[1]);
+ return;
+ }
+ if (nLoop == 2 &&
+ loop[1]->objType == DO_TABLE &&
+ loop[0]->objType == DO_RULE &&
+ (((TableInfo *) loop[1])->relkind == 'v' || /* RELKIND_VIEW */
+ ((TableInfo *) loop[1])->relkind == 'm') && /* RELKIND_MATVIEW */
+ ((RuleInfo *) loop[0])->ev_type == '1' &&
+ ((RuleInfo *) loop[0])->is_instead &&
+ ((RuleInfo *) loop[0])->ruletable == (TableInfo *) loop[1])
+ {
+ repairViewRuleLoop(loop[1], loop[0]);
+ return;
+ }
+
+ /* Indirect loop involving view (but not matview) and ON SELECT rule */
+ if (nLoop > 2)
+ {
+ for (i = 0; i < nLoop; i++)
+ {
+ if (loop[i]->objType == DO_TABLE &&
+ ((TableInfo *) loop[i])->relkind == 'v') /* RELKIND_VIEW */
+ {
+ for (j = 0; j < nLoop; j++)
+ {
+ if (loop[j]->objType == DO_RULE &&
+ ((RuleInfo *) loop[j])->ev_type == '1' &&
+ ((RuleInfo *) loop[j])->is_instead &&
+ ((RuleInfo *) loop[j])->ruletable == (TableInfo *) loop[i])
+ {
+ repairViewRuleMultiLoop(loop[i], loop[j]);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /* Indirect loop involving matview and data boundary */
+ if (nLoop > 2)
+ {
+ for (i = 0; i < nLoop; i++)
+ {
+ if (loop[i]->objType == DO_TABLE &&
+ ((TableInfo *) loop[i])->relkind == 'm') /* RELKIND_MATVIEW */
+ {
+ for (j = 0; j < nLoop; j++)
+ {
+ if (loop[j]->objType == DO_PRE_DATA_BOUNDARY)
+ {
+ DumpableObject *nextobj;
+
+ nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0];
+ repairMatViewBoundaryMultiLoop(loop[i], loop[j],
+ nextobj);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /* Table and CHECK constraint */
+ if (nLoop == 2 &&
+ loop[0]->objType == DO_TABLE &&
+ loop[1]->objType == DO_CONSTRAINT &&
+ ((ConstraintInfo *) loop[1])->contype == 'c' &&
+ ((ConstraintInfo *) loop[1])->contable == (TableInfo *) loop[0])
+ {
+ repairTableConstraintLoop(loop[0], loop[1]);
+ return;
+ }
+ if (nLoop == 2 &&
+ loop[1]->objType == DO_TABLE &&
+ loop[0]->objType == DO_CONSTRAINT &&
+ ((ConstraintInfo *) loop[0])->contype == 'c' &&
+ ((ConstraintInfo *) loop[0])->contable == (TableInfo *) loop[1])
+ {
+ repairTableConstraintLoop(loop[1], loop[0]);
+ return;
+ }
+
+ /* Indirect loop involving table and CHECK constraint */
+ if (nLoop > 2)
+ {
+ for (i = 0; i < nLoop; i++)
+ {
+ if (loop[i]->objType == DO_TABLE)
+ {
+ for (j = 0; j < nLoop; j++)
+ {
+ if (loop[j]->objType == DO_CONSTRAINT &&
+ ((ConstraintInfo *) loop[j])->contype == 'c' &&
+ ((ConstraintInfo *) loop[j])->contable == (TableInfo *) loop[i])
+ {
+ repairTableConstraintMultiLoop(loop[i], loop[j]);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /* Table and attribute default */
+ if (nLoop == 2 &&
+ loop[0]->objType == DO_TABLE &&
+ loop[1]->objType == DO_ATTRDEF &&
+ ((AttrDefInfo *) loop[1])->adtable == (TableInfo *) loop[0])
+ {
+ repairTableAttrDefLoop(loop[0], loop[1]);
+ return;
+ }
+ if (nLoop == 2 &&
+ loop[1]->objType == DO_TABLE &&
+ loop[0]->objType == DO_ATTRDEF &&
+ ((AttrDefInfo *) loop[0])->adtable == (TableInfo *) loop[1])
+ {
+ repairTableAttrDefLoop(loop[1], loop[0]);
+ return;
+ }
+
+ /* Indirect loop involving table and attribute default */
+ if (nLoop > 2)
+ {
+ for (i = 0; i < nLoop; i++)
+ {
+ if (loop[i]->objType == DO_TABLE)
+ {
+ for (j = 0; j < nLoop; j++)
+ {
+ if (loop[j]->objType == DO_ATTRDEF &&
+ ((AttrDefInfo *) loop[j])->adtable == (TableInfo *) loop[i])
+ {
+ repairTableAttrDefMultiLoop(loop[i], loop[j]);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /* Domain and CHECK constraint */
+ if (nLoop == 2 &&
+ loop[0]->objType == DO_TYPE &&
+ loop[1]->objType == DO_CONSTRAINT &&
+ ((ConstraintInfo *) loop[1])->contype == 'c' &&
+ ((ConstraintInfo *) loop[1])->condomain == (TypeInfo *) loop[0])
+ {
+ repairDomainConstraintLoop(loop[0], loop[1]);
+ return;
+ }
+ if (nLoop == 2 &&
+ loop[1]->objType == DO_TYPE &&
+ loop[0]->objType == DO_CONSTRAINT &&
+ ((ConstraintInfo *) loop[0])->contype == 'c' &&
+ ((ConstraintInfo *) loop[0])->condomain == (TypeInfo *) loop[1])
+ {
+ repairDomainConstraintLoop(loop[1], loop[0]);
+ return;
+ }
+
+ /* Indirect loop involving domain and CHECK constraint */
+ if (nLoop > 2)
+ {
+ for (i = 0; i < nLoop; i++)
+ {
+ if (loop[i]->objType == DO_TYPE)
+ {
+ for (j = 0; j < nLoop; j++)
+ {
+ if (loop[j]->objType == DO_CONSTRAINT &&
+ ((ConstraintInfo *) loop[j])->contype == 'c' &&
+ ((ConstraintInfo *) loop[j])->condomain == (TypeInfo *) loop[i])
+ {
+ repairDomainConstraintMultiLoop(loop[i], loop[j]);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * If all the objects are TABLE_DATA items, what we must have is a
+ * circular set of foreign key constraints (or a single self-referential
+ * table). Print an appropriate complaint and break the loop arbitrarily.
+ */
+ for (i = 0; i < nLoop; i++)
+ {
+ if (loop[i]->objType != DO_TABLE_DATA)
+ break;
+ }
+ if (i >= nLoop)
+ {
+ write_msg(NULL, "NOTICE: there are circular foreign-key constraints among these table(s):\n");
+ for (i = 0; i < nLoop; i++)
+ write_msg(NULL, " %s\n", loop[i]->name);
+ write_msg(NULL, "You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints.\n");
+ write_msg(NULL, "Consider using a full dump instead of a --data-only dump to avoid this problem.\n");
+ if (nLoop > 1)
+ removeObjectDependency(loop[0], loop[1]->dumpId);
+ else /* must be a self-dependency */
+ removeObjectDependency(loop[0], loop[0]->dumpId);
+ return;
+ }
+
+ /*
+ * If we can't find a principled way to break the loop, complain and break
+ * it in an arbitrary fashion.
+ */
+ write_msg(modulename, "WARNING: could not resolve dependency loop among these items:\n");
+ for (i = 0; i < nLoop; i++)
+ {
+ char buf[1024];
+
+ describeDumpableObject(loop[i], buf, sizeof(buf));
+ write_msg(modulename, " %s\n", buf);
+ }
+
+ if (nLoop > 1)
+ removeObjectDependency(loop[0], loop[1]->dumpId);
+ else /* must be a self-dependency */
+ removeObjectDependency(loop[0], loop[0]->dumpId);
+}
+
+/*
+ * Describe a dumpable object usefully for errors
+ *
+ * This should probably go somewhere else...
+ */
+static void
+describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
+{
+ switch (obj->objType)
+ {
+ case DO_NAMESPACE:
+ snprintf(buf, bufsize,
+ "SCHEMA %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_EXTENSION:
+ snprintf(buf, bufsize,
+ "EXTENSION %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TYPE:
+ snprintf(buf, bufsize,
+ "TYPE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_SHELL_TYPE:
+ snprintf(buf, bufsize,
+ "SHELL TYPE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_FUNC:
+ snprintf(buf, bufsize,
+ "FUNCTION %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_AGG:
+ snprintf(buf, bufsize,
+ "AGGREGATE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_OPERATOR:
+ snprintf(buf, bufsize,
+ "OPERATOR %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_OPCLASS:
+ snprintf(buf, bufsize,
+ "OPERATOR CLASS %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_OPFAMILY:
+ snprintf(buf, bufsize,
+ "OPERATOR FAMILY %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_COLLATION:
+ snprintf(buf, bufsize,
+ "COLLATION %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_CONVERSION:
+ snprintf(buf, bufsize,
+ "CONVERSION %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TABLE:
+ snprintf(buf, bufsize,
+ "TABLE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_ATTRDEF:
+ snprintf(buf, bufsize,
+ "ATTRDEF %s.%s (ID %d OID %u)",
+ ((AttrDefInfo *) obj)->adtable->dobj.name,
+ ((AttrDefInfo *) obj)->adtable->attnames[((AttrDefInfo *) obj)->adnum - 1],
+ obj->dumpId, obj->catId.oid);
+ return;
+ case DO_INDEX:
+ snprintf(buf, bufsize,
+ "INDEX %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_REFRESH_MATVIEW:
+ snprintf(buf, bufsize,
+ "REFRESH MATERIALIZED VIEW %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_RULE:
+ snprintf(buf, bufsize,
+ "RULE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TRIGGER:
+ snprintf(buf, bufsize,
+ "TRIGGER %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_EVENT_TRIGGER:
+ snprintf(buf, bufsize,
+ "EVENT TRIGGER %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_CONSTRAINT:
+ snprintf(buf, bufsize,
+ "CONSTRAINT %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_FK_CONSTRAINT:
+ snprintf(buf, bufsize,
+ "FK CONSTRAINT %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_PROCLANG:
+ snprintf(buf, bufsize,
+ "PROCEDURAL LANGUAGE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_CAST:
+ snprintf(buf, bufsize,
+ "CAST %u to %u (ID %d OID %u)",
+ ((CastInfo *) obj)->castsource,
+ ((CastInfo *) obj)->casttarget,
+ obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TABLE_DATA:
+ snprintf(buf, bufsize,
+ "TABLE DATA %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_DUMMY_TYPE:
+ snprintf(buf, bufsize,
+ "DUMMY TYPE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TSPARSER:
+ snprintf(buf, bufsize,
+ "TEXT SEARCH PARSER %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TSDICT:
+ snprintf(buf, bufsize,
+ "TEXT SEARCH DICTIONARY %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TSTEMPLATE:
+ snprintf(buf, bufsize,
+ "TEXT SEARCH TEMPLATE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_TSCONFIG:
+ snprintf(buf, bufsize,
+ "TEXT SEARCH CONFIGURATION %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_FDW:
+ snprintf(buf, bufsize,
+ "FOREIGN DATA WRAPPER %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_FOREIGN_SERVER:
+ snprintf(buf, bufsize,
+ "FOREIGN SERVER %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_DEFAULT_ACL:
+ snprintf(buf, bufsize,
+ "DEFAULT ACL %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_BLOB:
+ snprintf(buf, bufsize,
+ "BLOB (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
+ case DO_BLOB_DATA:
+ snprintf(buf, bufsize,
+ "BLOB DATA (ID %d)",
+ obj->dumpId);
+ return;
+ case DO_PRE_DATA_BOUNDARY:
+ snprintf(buf, bufsize,
+ "PRE-DATA BOUNDARY (ID %d)",
+ obj->dumpId);
+ return;
+ case DO_POST_DATA_BOUNDARY:
+ snprintf(buf, bufsize,
+ "POST-DATA BOUNDARY (ID %d)",
+ obj->dumpId);
+ return;
+ }
+ /* shouldn't get here */
+ snprintf(buf, bufsize,
+ "object type %d (ID %d OID %u)",
+ (int) obj->objType,
+ obj->dumpId, obj->catId.oid);
+}
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/pgtar.h
@@ -0,0 +1,17 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgtar.h
+ * Functions for manipulating tarfile datastructures (src/port/tar.c)
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/pgtar.h
+ *
+ *-------------------------------------------------------------------------
+ */
+extern void tarCreateHeader(char *h, const char *filename, const char *linktarget,
+ pgoff_t size, mode_t mode, uid_t uid, gid_t gid, time_t mtime);
+extern uint64 read_tar_number(const char *s, int len);
+extern int tarChecksum(char *header);
--- /dev/null
+++ pglogical-2.2.2/pglogical_dump/tar.c
@@ -0,0 +1,196 @@
+#include "c.h"
+#include "pgtar.h"
+#include <sys/stat.h>
+
+/*
+ * Print a numeric field in a tar header. The field starts at *s and is of
+ * length len; val is the value to be written.
+ *
+ * Per POSIX, the way to write a number is in octal with leading zeroes and
+ * one trailing space (or NUL, but we use space) at the end of the specified
+ * field width.
+ *
+ * However, the given value may not fit in the available space in octal form.
+ * If that's true, we use the GNU extension of writing \200 followed by the
+ * number in base-256 form (ie, stored in binary MSB-first). (Note: here we
+ * support only non-negative numbers, so we don't worry about the GNU rules
+ * for handling negative numbers.)
+ */
+static void
+print_tar_number(char *s, int len, uint64 val)
+{
+ if (val < (((uint64) 1) << ((len - 1) * 3)))
+ {
+ /* Use octal with trailing space */
+ s[--len] = ' ';
+ while (len)
+ {
+ s[--len] = (val & 7) + '0';
+ val >>= 3;
+ }
+ }
+ else
+ {
+ /* Use base-256 with leading \200 */
+ s[0] = '\200';
+ while (len > 1)
+ {
+ s[--len] = (val & 255);
+ val >>= 8;
+ }
+ }
+}
+
+
+/*
+ * Read a numeric field in a tar header. The field starts at *s and is of
+ * length len.
+ *
+ * The POSIX-approved format for a number is octal, ending with a space or
+ * NUL. However, for values that don't fit, we recognize the GNU extension
+ * of \200 followed by the number in base-256 form (ie, stored in binary
+ * MSB-first). (Note: here we support only non-negative numbers, so we don't
+ * worry about the GNU rules for handling negative numbers.)
+ */
+uint64
+read_tar_number(const char *s, int len)
+{
+ uint64 result = 0;
+
+ if (*s == '\200')
+ {
+ /* base-256 */
+ while (--len)
+ {
+ result <<= 8;
+ result |= (unsigned char) (*++s);
+ }
+ }
+ else
+ {
+ /* octal */
+ while (len-- && *s >= '0' && *s <= '7')
+ {
+ result <<= 3;
+ result |= (*s - '0');
+ s++;
+ }
+ }
+ return result;
+}
+
+
+/*
+ * Calculate the tar checksum for a header. The header is assumed to always
+ * be 512 bytes, per the tar standard.
+ */
+int
+tarChecksum(char *header)
+{
+ int i,
+ sum;
+
+ /*
+ * Per POSIX, the checksum is the simple sum of all bytes in the header,
+ * treating the bytes as unsigned, and treating the checksum field (at
+ * offset 148) as though it contained 8 spaces.
+ */
+ sum = 8 * ' '; /* presumed value for checksum field */
+ for (i = 0; i < 512; i++)
+ if (i < 148 || i >= 156)
+ sum += 0xFF & header[i];
+ return sum;
+}
+
+
+/*
+ * Fill in the buffer pointed to by h with a tar format header. This buffer
+ * must always have space for 512 characters, which is a requirement of
+ * the tar format.
+ */
+void
+tarCreateHeader(char *h, const char *filename, const char *linktarget,
+ pgoff_t size, mode_t mode, uid_t uid, gid_t gid, time_t mtime)
+{
+ memset(h, 0, 512); /* assume tar header size */
+
+ /* Name 100 */
+ strlcpy(&h[0], filename, 100);
+ if (linktarget != NULL || S_ISDIR(mode))
+ {
+ /*
+ * We only support symbolic links to directories, and this is
+ * indicated in the tar format by adding a slash at the end of the
+ * name, the same as for regular directories.
+ */
+ int flen = strlen(filename);
+
+ flen = Min(flen, 99);
+ h[flen] = '/';
+ h[flen + 1] = '\0';
+ }
+
+ /* Mode 8 - this doesn't include the file type bits (S_IFMT) */
+ print_tar_number(&h[100], 8, (mode & 07777));
+
+ /* User ID 8 */
+ print_tar_number(&h[108], 8, uid);
+
+ /* Group 8 */
+ print_tar_number(&h[116], 8, gid);
+
+ /* File size 12 */
+ if (linktarget != NULL || S_ISDIR(mode))
+ /* Symbolic link or directory has size zero */
+ print_tar_number(&h[124], 12, 0);
+ else
+ print_tar_number(&h[124], 12, size);
+
+ /* Mod Time 12 */
+ print_tar_number(&h[136], 12, mtime);
+
+ /* Checksum 8 cannot be calculated until we've filled all other fields */
+
+ if (linktarget != NULL)
+ {
+ /* Type - Symbolic link */
+ h[156] = '2';
+ /* Link Name 100 */
+ strlcpy(&h[157], linktarget, 100);
+ }
+ else if (S_ISDIR(mode))
+ {
+ /* Type - directory */
+ h[156] = '5';
+ }
+ else
+ {
+ /* Type - regular file */
+ h[156] = '0';
+ }
+
+ /* Magic 6 */
+ strcpy(&h[257], "ustar");
+
+ /* Version 2 */
+ memcpy(&h[263], "00", 2);
+
+ /* User 32 */
+ /* XXX: Do we need to care about setting correct username? */
+ strlcpy(&h[265], "postgres", 32);
+
+ /* Group 32 */
+ /* XXX: Do we need to care about setting correct group name? */
+ strlcpy(&h[297], "postgres", 32);
+
+ /* Major Dev 8 */
+ print_tar_number(&h[329], 8, 0);
+
+ /* Minor Dev 8 */
+ print_tar_number(&h[337], 8, 0);
+
+ /* Prefix 155 - not used, leave as nulls */
+
+ /* Finally, compute and insert the checksum */
+ print_tar_number(&h[148], 8, tarChecksum(h));
+}