| /*------------------------------------------------------------------------- |
| * |
| * common.c |
| * Catalog routines used by pg_dump; long ago these were shared |
| * by another dump tool, but not anymore. |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/bin/pg_dump/common.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres_fe.h" |
| |
| #include <ctype.h> |
| |
| #include "catalog/pg_class_d.h" |
| #include "fe_utils/string_utils.h" |
| #include "pg_backup_archiver.h" |
| #include "pg_backup_utils.h" |
| #include "pg_dump.h" |
| |
| /* |
| * Variables for mapping DumpId to DumpableObject |
| */ |
| static DumpableObject **dumpIdMap = NULL; |
| static int allocedDumpIds = 0; |
| static DumpId lastDumpId = 0; /* Note: 0 is InvalidDumpId */ |
| |
| /* |
| * 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 the objects are fetched, |
| * and then we use binary search in findTableByOid() and friends. (qsort'ing |
| * the object arrays themselves would be simpler, but it doesn't work because |
| * pg_dump.c may have already established pointers between items.) |
| */ |
| static DumpableObject **tblinfoindex; |
| static DumpableObject **typinfoindex; |
| static DumpableObject **funinfoindex; |
| static DumpableObject **oprinfoindex; |
| static DumpableObject **collinfoindex; |
| static DumpableObject **nspinfoindex; |
| static DumpableObject **extinfoindex; |
| static DumpableObject **pubinfoindex; |
| static DumpableObject **binaryupgradeinfoindex; |
| |
| static int numTables; |
| static int numTypes; |
| static int numFuncs; |
| static int numOperators; |
| static int numCollations; |
| static int numNamespaces; |
| static int numExtensions; |
| static int numPublications; |
| static int numTypeStorageOptions; |
| |
| |
| /* This is an array of object identities, not actual DumpableObjects */ |
| static ExtensionMemberId *extmembers; |
| static int numextmembers; |
| |
| static void flagInhTables(Archive *fout, TableInfo *tbinfo, int numTables, |
| InhInfo *inhinfo, int numInherits); |
| static void flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables); |
| static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables); |
| static DumpableObject **buildIndexArray(void *objArray, int numObjs, |
| Size objSize); |
| static int DOCatalogIdCompare(const void *p1, const void *p2); |
| static int ExtensionMemberIdCompare(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); |
| static IndxInfo *findIndexByOid(Oid oid, DumpableObject **idxinfoindex, |
| int numIndexes); |
| |
| |
| /* |
| * getSchemaData |
| * Collect information about all potentially dumpable objects |
| */ |
| TableInfo * |
| getSchemaData(Archive *fout, int *numTablesPtr) |
| { |
| TableInfo *tblinfo; |
| TypeInfo *typinfo; |
| FuncInfo *funinfo; |
| OprInfo *oprinfo; |
| CollInfo *collinfo; |
| NamespaceInfo *nspinfo; |
| ExtensionInfo *extinfo; |
| PublicationInfo *pubinfo; |
| InhInfo *inhinfo; |
| int numAggregates; |
| int numInherits; |
| int numRules; |
| int numProcLangs; |
| int numCasts; |
| int numTransforms; |
| int numAccessMethods; |
| int numOpclasses; |
| int numOpfamilies; |
| int numConversions; |
| int numTSParsers; |
| int numTSTemplates; |
| int numTSDicts; |
| int numTSConfigs; |
| int numForeignDataWrappers; |
| int numForeignServers; |
| int numDefaultACLs; |
| int numEventTriggers; |
| |
| /* GPDB specific variables */ |
| int numExtProtocols; |
| |
| if (fout->dopt->binary_upgrade) |
| { |
| BinaryUpgradeInfo *binfo; |
| |
| pg_log_info("identifying required binary upgrade calls"); |
| |
| binfo = getBinaryUpgradeObjects(); |
| binaryupgradeinfoindex = buildIndexArray(binfo, 1, sizeof(BinaryUpgradeInfo)); |
| } |
| |
| /* |
| * We must read extensions and extension membership info first, because |
| * extension membership needs to be consultable during decisions about |
| * whether other objects are to be dumped. |
| */ |
| pg_log_info("reading extensions"); |
| extinfo = getExtensions(fout, &numExtensions); |
| extinfoindex = buildIndexArray(extinfo, numExtensions, sizeof(ExtensionInfo)); |
| |
| pg_log_info("identifying extension members"); |
| getExtensionMembership(fout, extinfo, numExtensions); |
| |
| pg_log_info("reading schemas"); |
| 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. |
| */ |
| pg_log_info("reading user-defined tables"); |
| tblinfo = getTables(fout, &numTables); |
| tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo)); |
| |
| /* Do this after we've built tblinfoindex */ |
| getOwnedSeqs(fout, tblinfo, numTables); |
| |
| pg_log_info("reading user-defined functions"); |
| funinfo = getFuncs(fout, &numFuncs); |
| funinfoindex = buildIndexArray(funinfo, numFuncs, sizeof(FuncInfo)); |
| |
| /* this must be after getTables and getFuncs */ |
| pg_log_info("reading user-defined types"); |
| typinfo = getTypes(fout, &numTypes); |
| typinfoindex = buildIndexArray(typinfo, numTypes, sizeof(TypeInfo)); |
| |
| /* this must be after getFuncs */ |
| pg_log_info("reading type storage options"); |
| getTypeStorageOptions(fout, &numTypeStorageOptions); |
| |
| /* this must be after getFuncs, too */ |
| pg_log_info("reading procedural languages"); |
| getProcLangs(fout, &numProcLangs); |
| |
| pg_log_info("reading user-defined aggregate functions"); |
| getAggregates(fout, &numAggregates); |
| |
| pg_log_info("reading user-defined operators"); |
| oprinfo = getOperators(fout, &numOperators); |
| oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo)); |
| |
| if (testExtProtocolSupport(fout)) |
| { |
| pg_log_info("reading user-defined external protocols"); |
| getExtProtocols(fout, &numExtProtocols); |
| } |
| |
| pg_log_info("reading user-defined access methods"); |
| getAccessMethods(fout, &numAccessMethods); |
| |
| pg_log_info("reading user-defined operator classes"); |
| getOpclasses(fout, &numOpclasses); |
| |
| pg_log_info("reading user-defined operator families"); |
| getOpfamilies(fout, &numOpfamilies); |
| |
| pg_log_info("reading user-defined text search parsers"); |
| getTSParsers(fout, &numTSParsers); |
| |
| pg_log_info("reading user-defined text search templates"); |
| getTSTemplates(fout, &numTSTemplates); |
| |
| pg_log_info("reading user-defined text search dictionaries"); |
| getTSDictionaries(fout, &numTSDicts); |
| |
| pg_log_info("reading user-defined text search configurations"); |
| getTSConfigurations(fout, &numTSConfigs); |
| |
| pg_log_info("reading user-defined foreign-data wrappers"); |
| getForeignDataWrappers(fout, &numForeignDataWrappers); |
| |
| pg_log_info("reading user-defined foreign servers"); |
| getForeignServers(fout, &numForeignServers); |
| |
| pg_log_info("reading default privileges"); |
| getDefaultACLs(fout, &numDefaultACLs); |
| |
| pg_log_info("reading user-defined collations"); |
| collinfo = getCollations(fout, &numCollations); |
| collinfoindex = buildIndexArray(collinfo, numCollations, sizeof(CollInfo)); |
| |
| pg_log_info("reading user-defined conversions"); |
| getConversions(fout, &numConversions); |
| |
| pg_log_info("reading type casts"); |
| getCasts(fout, &numCasts); |
| |
| pg_log_info("reading transforms"); |
| getTransforms(fout, &numTransforms); |
| |
| pg_log_info("reading table inheritance information"); |
| inhinfo = getInherits(fout, &numInherits); |
| |
| pg_log_info("reading event triggers"); |
| getEventTriggers(fout, &numEventTriggers); |
| |
| /* Identify extension configuration tables that should be dumped */ |
| pg_log_info("finding extension tables"); |
| processExtensionTables(fout, extinfo, numExtensions); |
| |
| /* Link tables to parents, mark parents of target tables interesting */ |
| pg_log_info("finding inheritance relationships"); |
| flagInhTables(fout, tblinfo, numTables, inhinfo, numInherits); |
| |
| pg_log_info("reading column info for interesting tables"); |
| getTableAttrs(fout, tblinfo, numTables); |
| |
| pg_log_info("flagging inherited columns in subtables"); |
| flagInhAttrs(fout->dopt, tblinfo, numTables); |
| |
| pg_log_info("reading indexes"); |
| getIndexes(fout, tblinfo, numTables); |
| |
| pg_log_info("flagging indexes in partitioned tables"); |
| flagInhIndexes(fout, tblinfo, numTables); |
| |
| pg_log_info("reading extended statistics"); |
| getExtendedStatistics(fout); |
| |
| pg_log_info("reading constraints"); |
| getConstraints(fout, tblinfo, numTables); |
| |
| pg_log_info("reading triggers"); |
| getTriggers(fout, tblinfo, numTables); |
| |
| pg_log_info("reading rewrite rules"); |
| getRules(fout, &numRules); |
| |
| pg_log_info("reading policies"); |
| getPolicies(fout, tblinfo, numTables); |
| |
| pg_log_info("reading publications"); |
| pubinfo = getPublications(fout, &numPublications); |
| pubinfoindex = buildIndexArray(pubinfo, numPublications, |
| sizeof(PublicationInfo)); |
| |
| pg_log_info("reading publication membership"); |
| getPublicationTables(fout, tblinfo, numTables); |
| |
| pg_log_info("reading subscriptions"); |
| getSubscriptions(fout); |
| |
| *numTablesPtr = numTables; |
| return tblinfo; |
| } |
| |
| /* flagInhTables - |
| * Fill in parent link fields of tables for which we need that information, |
| * mark parents of target tables as interesting, and create |
| * TableAttachInfo objects for partitioned tables with appropriate |
| * dependency links. |
| * |
| * 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(Archive *fout, TableInfo *tblinfo, int numTables, |
| InhInfo *inhinfo, int numInherits) |
| { |
| DumpOptions *dopt = fout->dopt; |
| int i, |
| j; |
| |
| for (i = 0; i < numTables; i++) |
| { |
| bool find_parents = true; |
| bool mark_parents = true; |
| |
| /* Some kinds never have parents */ |
| if (tblinfo[i].relkind == RELKIND_SEQUENCE || |
| tblinfo[i].relkind == RELKIND_VIEW || |
| tblinfo[i].relkind == RELKIND_MATVIEW) |
| continue; |
| |
| /* |
| * FIXME: In PostgreSQL, foreign tables can be inherited. But |
| * pg_dump chokes on external tables, if an external table is |
| * used as a partition, and a column has attislocal=false. |
| */ |
| if (tblinfo[i].relstorage == 'x' /* RELSTORAGE_EXTERNAL */) |
| continue; |
| |
| /* |
| * Normally, we don't bother computing anything for non-target tables, |
| * but if load-via-partition-root is specified, we gather information |
| * on every partition in the system so that getRootTableInfo can trace |
| * from any given to leaf partition all the way up to the root. (We |
| * don't need to mark them as interesting for getTableAttrs, though.) |
| */ |
| if (!tblinfo[i].dobj.dump) |
| { |
| mark_parents = false; |
| |
| if (!dopt->load_via_partition_root || |
| !tblinfo[i].ispartition) |
| find_parents = false; |
| } |
| |
| /* If needed, find all the immediate parent tables. */ |
| if (find_parents) |
| findParentsByOid(&tblinfo[i], inhinfo, numInherits); |
| |
| /* |
| * If needed, mark the parents as interesting for getTableAttrs and |
| * getIndexes. |
| */ |
| if (mark_parents) |
| { |
| int numParents = tblinfo[i].numParents; |
| TableInfo **parents = tblinfo[i].parents; |
| |
| for (j = 0; j < numParents; j++) |
| parents[j]->interesting = true; |
| } |
| |
| /* Create TableAttachInfo object if needed */ |
| if (tblinfo[i].dobj.dump && tblinfo[i].ispartition) |
| { |
| TableAttachInfo *attachinfo; |
| |
| /* With partitions there can only be one parent */ |
| if (tblinfo[i].numParents != 1) |
| fatal("invalid number of parents %d for table \"%s\"", |
| tblinfo[i].numParents, |
| tblinfo[i].dobj.name); |
| |
| attachinfo = (TableAttachInfo *) palloc(sizeof(TableAttachInfo)); |
| attachinfo->dobj.objType = DO_TABLE_ATTACH; |
| attachinfo->dobj.catId.tableoid = 0; |
| attachinfo->dobj.catId.oid = 0; |
| AssignDumpId(&attachinfo->dobj); |
| attachinfo->dobj.name = pg_strdup(tblinfo[i].dobj.name); |
| attachinfo->dobj.namespace = tblinfo[i].dobj.namespace; |
| attachinfo->parentTbl = tblinfo[i].parents[0]; |
| attachinfo->partitionTbl = &tblinfo[i]; |
| |
| /* |
| * We must state the DO_TABLE_ATTACH object's dependencies |
| * explicitly, since it will not match anything in pg_depend. |
| * |
| * Give it dependencies on both the partition table and the parent |
| * table, so that it will not be executed till both of those |
| * exist. (There's no need to care what order those are created |
| * in.) |
| */ |
| addObjectDependency(&attachinfo->dobj, tblinfo[i].dobj.dumpId); |
| addObjectDependency(&attachinfo->dobj, tblinfo[i].parents[0]->dobj.dumpId); |
| } |
| } |
| } |
| |
| /* |
| * flagInhIndexes - |
| * Create IndexAttachInfo objects for partitioned indexes, and add |
| * appropriate dependency links. |
| */ |
| static void |
| flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables) |
| { |
| int i, |
| j, |
| k; |
| DumpableObject ***parentIndexArray; |
| |
| parentIndexArray = (DumpableObject ***) |
| pg_malloc0(getMaxDumpId() * sizeof(DumpableObject **)); |
| |
| for (i = 0; i < numTables; i++) |
| { |
| TableInfo *parenttbl; |
| IndexAttachInfo *attachinfo; |
| |
| if (!tblinfo[i].ispartition || tblinfo[i].numParents == 0) |
| continue; |
| |
| Assert(tblinfo[i].numParents == 1); |
| parenttbl = tblinfo[i].parents[0]; |
| |
| /* |
| * We need access to each parent table's index list, but there is no |
| * index to cover them outside of this function. To avoid having to |
| * sort every parent table's indexes each time we come across each of |
| * its partitions, create an indexed array for each parent the first |
| * time it is required. |
| */ |
| if (parentIndexArray[parenttbl->dobj.dumpId] == NULL) |
| parentIndexArray[parenttbl->dobj.dumpId] = |
| buildIndexArray(parenttbl->indexes, |
| parenttbl->numIndexes, |
| sizeof(IndxInfo)); |
| |
| attachinfo = (IndexAttachInfo *) |
| pg_malloc0(tblinfo[i].numIndexes * sizeof(IndexAttachInfo)); |
| for (j = 0, k = 0; j < tblinfo[i].numIndexes; j++) |
| { |
| IndxInfo *index = &(tblinfo[i].indexes[j]); |
| IndxInfo *parentidx; |
| |
| if (index->parentidx == 0) |
| continue; |
| |
| parentidx = findIndexByOid(index->parentidx, |
| parentIndexArray[parenttbl->dobj.dumpId], |
| parenttbl->numIndexes); |
| if (parentidx == NULL) |
| continue; |
| |
| attachinfo[k].dobj.objType = DO_INDEX_ATTACH; |
| attachinfo[k].dobj.catId.tableoid = 0; |
| attachinfo[k].dobj.catId.oid = 0; |
| AssignDumpId(&attachinfo[k].dobj); |
| attachinfo[k].dobj.name = pg_strdup(index->dobj.name); |
| attachinfo[k].dobj.namespace = index->indextable->dobj.namespace; |
| attachinfo[k].parentIdx = parentidx; |
| attachinfo[k].partitionIdx = index; |
| |
| /* |
| * We must state the DO_INDEX_ATTACH object's dependencies |
| * explicitly, since it will not match anything in pg_depend. |
| * |
| * Give it dependencies on both the partition index and the parent |
| * index, so that it will not be executed till both of those |
| * exist. (There's no need to care what order those are created |
| * in.) |
| * |
| * In addition, give it dependencies on the indexes' underlying |
| * tables. This does nothing of great value so far as serial |
| * restore ordering goes, but it ensures that a parallel restore |
| * will not try to run the ATTACH concurrently with other |
| * operations on those tables. |
| */ |
| addObjectDependency(&attachinfo[k].dobj, index->dobj.dumpId); |
| addObjectDependency(&attachinfo[k].dobj, parentidx->dobj.dumpId); |
| addObjectDependency(&attachinfo[k].dobj, |
| index->indextable->dobj.dumpId); |
| addObjectDependency(&attachinfo[k].dobj, |
| parentidx->indextable->dobj.dumpId); |
| |
| /* keep track of the list of partitions in the parent index */ |
| simple_ptr_list_append(&parentidx->partattaches, &attachinfo[k].dobj); |
| |
| k++; |
| } |
| } |
| |
| for (i = 0; i < numTables; i++) |
| if (parentIndexArray[i]) |
| pg_free(parentIndexArray[i]); |
| pg_free(parentIndexArray); |
| } |
| |
| /* 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. |
| * |
| * - Detect child columns that have DEFAULT NULL when their parents had some |
| * non-null default. In this 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. |
| * |
| * - Detect child columns that have a generation expression when their parents |
| * also have one. Generation expressions are always inherited, so there is |
| * no need to set them again in child tables, and there is no syntax for it |
| * either. Exceptions: If it's a partition or we are in binary upgrade |
| * mode, we dump them because in those cases inherited tables are recreated |
| * standalone first and then reattached to the parent. (See also the logic |
| * in dumpTableSchema().) In that situation, the generation expressions |
| * must match the parent, enforced by ALTER TABLE. |
| * |
| * modifies tblinfo |
| */ |
| static void |
| flagInhAttrs(DumpOptions *dopt, 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; |
| |
| /* |
| * FIXME: In PostgreSQL, foreign tables can be inherited. But |
| * pg_dump chokes on external tables, if an external table is |
| * used as a partition, and a column has attislocal=false. |
| */ |
| if (tblinfo[i].relstorage == 'x' /* RELSTORAGE_EXTERNAL */) |
| 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 */ |
| bool foundGenerated; /* Found a generated in a parent */ |
| |
| /* no point in examining dropped columns */ |
| if (tbinfo->attisdropped[j]) |
| continue; |
| |
| foundNotNull = false; |
| foundDefault = false; |
| foundGenerated = 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 && !parent->attgenerated[inhAttrInd]); |
| foundGenerated |= parent->attgenerated[inhAttrInd]; |
| } |
| } |
| |
| /* 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 *) malloc(sizeof(AttrDefInfo)); |
| attrDef->dobj.objType = DO_ATTRDEF; |
| attrDef->dobj.catId.tableoid = 0; |
| attrDef->dobj.catId.oid = 0; |
| AssignDumpId(&attrDef->dobj); |
| attrDef->dobj.name = 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 = strdup("NULL"); |
| |
| /* Will column be dumped explicitly? */ |
| if (shouldPrintColumn(dopt, 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; |
| } |
| |
| /* Remove generation expression from child */ |
| if (foundGenerated && !tbinfo->ispartition && !dopt->binary_upgrade) |
| tbinfo->attrdefs[j] = NULL; |
| } |
| } |
| } |
| |
| /* |
| * 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 = DUMP_COMPONENT_ALL; /* default assumption */ |
| dobj->dump_contains = DUMP_COMPONENT_ALL; /* default assumption */ |
| dobj->ext_member = false; /* default assumption */ |
| dobj->depends_on_ext = 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; |
| |
| if (numObjs <= 0) |
| return NULL; |
| |
| 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); |
| } |
| |
| /* |
| * findExtensionByOid |
| * finds the entry (in extinfo) of the extension with the given oid |
| * returns NULL if not found |
| */ |
| ExtensionInfo * |
| findExtensionByOid(Oid oid) |
| { |
| return (ExtensionInfo *) findObjectByOid(oid, extinfoindex, numExtensions); |
| } |
| |
| /* |
| * findPublicationByOid |
| * finds the entry (in pubinfo) of the publication with the given oid |
| * returns NULL if not found |
| */ |
| PublicationInfo * |
| findPublicationByOid(Oid oid) |
| { |
| return (PublicationInfo *) findObjectByOid(oid, pubinfoindex, numPublications); |
| } |
| |
| /* |
| * findIndexByOid |
| * find the entry of the index with the given oid |
| * |
| * This one's signature is different from the previous ones because we lack a |
| * global array of all indexes, so caller must pass their array as argument. |
| */ |
| static IndxInfo * |
| findIndexByOid(Oid oid, DumpableObject **idxinfoindex, int numIndexes) |
| { |
| return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes); |
| } |
| |
| /* |
| * setExtensionMembership |
| * accept and save data about which objects belong to extensions |
| */ |
| void |
| setExtensionMembership(ExtensionMemberId *extmems, int nextmems) |
| { |
| /* Sort array in preparation for binary searches */ |
| if (nextmems > 1) |
| qsort((void *) extmems, nextmems, sizeof(ExtensionMemberId), |
| ExtensionMemberIdCompare); |
| /* And save */ |
| extmembers = extmems; |
| numextmembers = nextmems; |
| } |
| |
| /* |
| * findOwningExtension |
| * return owning extension for specified catalog ID, or NULL if none |
| */ |
| ExtensionInfo * |
| findOwningExtension(CatalogId catalogId) |
| { |
| ExtensionMemberId *low; |
| ExtensionMemberId *high; |
| |
| /* |
| * 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 (numextmembers <= 0) |
| return NULL; |
| low = extmembers; |
| high = extmembers + (numextmembers - 1); |
| while (low <= high) |
| { |
| ExtensionMemberId *middle; |
| int difference; |
| |
| middle = low + (high - low) / 2; |
| /* comparison must match ExtensionMemberIdCompare, below */ |
| difference = oidcmp(middle->catId.oid, catalogId.oid); |
| if (difference == 0) |
| difference = oidcmp(middle->catId.tableoid, catalogId.tableoid); |
| if (difference == 0) |
| return middle->ext; |
| else if (difference < 0) |
| low = middle + 1; |
| else |
| high = middle - 1; |
| } |
| return NULL; |
| } |
| |
| /* |
| * qsort comparator for ExtensionMemberIds |
| */ |
| static int |
| ExtensionMemberIdCompare(const void *p1, const void *p2) |
| { |
| const ExtensionMemberId *obj1 = (const ExtensionMemberId *) p1; |
| const ExtensionMemberId *obj2 = (const ExtensionMemberId *) 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; |
| } |
| |
| |
| /* |
| * 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) |
| { |
| pg_log_error("failed sanity check, parent OID %u of table \"%s\" (OID %u) not found", |
| 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) |
| { |
| pg_log_error("could not parse numeric array \"%s\": too many numbers", 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) |
| { |
| pg_log_error("could not parse numeric array \"%s\": invalid character in number", 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; |
| } |