Description: 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 --- 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: , Bug: Bug-Debian: https://bugs.debian.org/ Bug-Ubuntu: https://launchpad.net/bugs/ Forwarded: Reviewed-By: 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 + +#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 + +#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 + +#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 +#include +#include "signal.h" +#include +#include +#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 +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#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 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 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: type with tag; this is obsolete and we + * always ignore it. + */ + if ((strcmp(te->desc, "") == 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 + +#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 +#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 +#include +#ifdef HAVE_TERMIOS_H +#include +#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 ".dat". + * Large objects (BLOBs) are stored in separate files named "blob_.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 +#include + +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 /* 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 +#include +#include +#include + +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 +#include +#ifdef ENABLE_NLS +#include +#endif +#ifdef HAVE_TERMIOS_H +#include +#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, "e_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 .\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, ©buf, 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 + +/* + * 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)); +}