/*------------------------------------------------------------------------- * * pg_dump_sort.c * Sort the items of a dump into a safe order for dumping * * * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.29 2010/02/18 01:29:10 tgl Exp $ * *------------------------------------------------------------------------- */ #include "pg_backup_archiver.h" 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: text * search, foreign-data, and default ACL objects can't really happen here, * so the rather bogus priorities for them don't matter. */ static const int oldObjectTypePriority[] = { 1, /* DO_NAMESPACE */ 2, /* DO_TYPE */ 2, /* DO_SHELL_TYPE */ 2, /* DO_FUNC */ 3, /* DO_AGG */ 3, /* DO_OPERATOR */ 4, /* DO_OPCLASS */ 4, /* DO_OPFAMILY */ 5, /* DO_CONVERSION */ 6, /* DO_TABLE */ 8, /* DO_ATTRDEF */ 13, /* DO_INDEX */ 14, /* DO_RULE */ 15, /* DO_TRIGGER */ 12, /* DO_CONSTRAINT */ 16, /* DO_FK_CONSTRAINT */ 2, /* DO_PROCLANG */ 2, /* DO_CAST */ 10, /* DO_TABLE_DATA */ 7, /* DO_DUMMY_TYPE */ 3, /* DO_TSPARSER */ 4, /* DO_TSDICT */ 3, /* DO_TSTEMPLATE */ 5, /* DO_TSCONFIG */ 3, /* DO_FDW */ 4, /* DO_FOREIGN_SERVER */ 17, /* DO_DEFAULT_ACL */ 9, /* DO_BLOB */ 11 /* DO_BLOB_DATA */ }; /* * Sort priority for object types when dumping newer databases. * Objects are sorted by type, and within a type by name. */ static const int newObjectTypePriority[] = { 1, /* DO_NAMESPACE */ 3, /* DO_TYPE */ 3, /* DO_SHELL_TYPE */ 4, /* DO_FUNC */ 5, /* DO_AGG */ 6, /* DO_OPERATOR */ 7, /* DO_OPCLASS */ 7, /* DO_OPFAMILY */ 9, /* DO_CONVERSION */ 16, /* DO_TABLE */ 18, /* DO_ATTRDEF */ 23, /* DO_INDEX */ 24, /* DO_RULE */ 25, /* DO_TRIGGER */ 22, /* DO_CONSTRAINT */ 26, /* DO_FK_CONSTRAINT */ 2, /* DO_PROCLANG */ 8, /* DO_CAST */ 20, /* DO_TABLE_DATA */ 17, /* DO_DUMMY_TYPE */ 10, /* DO_TSPARSER */ 12, /* DO_TSDICT */ 11, /* DO_TSTEMPLATE */ 13, /* DO_TSCONFIG */ 14, /* DO_FDW */ 15, /* DO_FOREIGN_SERVER */ 27, /* DO_DEFAULT_ACL */ 19, /* DO_BLOB */ 21 /* DO_BLOB_DATA */ }; 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 bool findLoop(DumpableObject *obj, DumpId startPoint, DumpableObject **workspace, int depth, int *newDepth); static void repairDependencyLoop(DumpableObject **loop, int nLoop); static void describeDumpableObject(DumpableObject *obj, char *buf, int bufsize); /* * 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 **) p1; DumpableObject *obj2 = *(DumpableObject **) 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 **) p1; FuncInfo *fobj2 = *(FuncInfo **) p2; cmpval = fobj1->nargs - fobj2->nargs; 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 **) p1; DumpableObject *obj2 = *(DumpableObject **) 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). */ void sortDumpableObjects(DumpableObject **objs, int numObjs) { DumpableObject **ordering; int nOrdering; if (numObjs <= 0) return; ordering = (DumpableObject **) malloc(numObjs * sizeof(DumpableObject *)); if (ordering == NULL) exit_horribly(NULL, modulename, "out of memory\n"); 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 *) malloc(numObjs * sizeof(int)); if (pendingHeap == NULL) exit_horribly(NULL, modulename, "out of memory\n"); /* * 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 *) malloc((maxDumpId + 1) * sizeof(int)); if (beforeConstraints == NULL) exit_horribly(NULL, modulename, "out of memory\n"); memset(beforeConstraints, 0, (maxDumpId + 1) * sizeof(int)); idMap = (int *) malloc((maxDumpId + 1) * sizeof(int)); if (idMap == NULL) exit_horribly(NULL, modulename, "out of memory\n"); for (i = 0; i < numObjs; i++) { obj = objs[i]; j = obj->dumpId; if (j <= 0 || j > maxDumpId) exit_horribly(NULL, 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(NULL, 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 a workspace array, the initial part of which stores objects * already processed, and the rest of which is used as temporary space to * try to build a loop in. This is convenient because we do not care * about loops involving already-processed objects (see notes above); we * can easily reject such loops in findLoop() because of this * representation. After we identify and process a loop, we can add it to * the initial part of the workspace just by moving the boundary pointer. * * When we determine that an object is not part of any interesting loop, * we also add it to the initial part of the workspace. This is not * necessary for correctness, but saves later invocations of findLoop() * from uselessly chasing references to such an object. * * We make the workspace large enough to hold all objects in the original * universe. This is probably overkill, but it's provably enough space... */ DumpableObject **workspace; int initiallen; bool fixedloop; int i; workspace = (DumpableObject **) malloc(totObjs * sizeof(DumpableObject *)); if (workspace == NULL) exit_horribly(NULL, modulename, "out of memory\n"); initiallen = 0; fixedloop = false; for (i = 0; i < nObjs; i++) { DumpableObject *obj = objs[i]; int newlen; workspace[initiallen] = NULL; /* see test below */ if (findLoop(obj, obj->dumpId, workspace, initiallen, &newlen)) { /* Found a loop of length newlen - initiallen */ repairDependencyLoop(&workspace[initiallen], newlen - initiallen); /* Add loop members to workspace */ initiallen = newlen; fixedloop = true; } else { /* * Didn't find a loop, but add this object to workspace anyway, * unless it's already present. We piggyback on the test that * findLoop() already did: it won't have tentatively added obj to * workspace if it's already present. */ if (workspace[initiallen] == obj) initiallen++; } } /* We'd better have fixed at least one loop */ if (!fixedloop) exit_horribly(NULL, modulename, "could not identify dependency loop\n"); free(workspace); } /* * Recursively search for a circular dependency loop that doesn't include * any existing workspace members. * * obj: object we are examining now * startPoint: dumpId of starting object for the hoped-for circular loop * workspace[]: work array for previously processed and current objects * depth: number of valid entries in workspace[] at call * newDepth: if successful, set to new number of workspace[] entries * * On success, *newDepth is set and workspace[] entries depth..*newDepth-1 * are filled with pointers to the members of the loop. * * 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 bool findLoop(DumpableObject *obj, DumpId startPoint, DumpableObject **workspace, int depth, int *newDepth) { int i; /* * Reject if obj is already present in workspace. This test serves three * purposes: it prevents us from finding loops that overlap * previously-processed loops, it 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 false; } /* * 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) { *newDepth = depth; return true; } } /* * Recurse down each outgoing branch */ for (i = 0; i < obj->nDeps; i++) { DumpableObject *nextobj = findObjectByDumpId(obj->dependencies[i]); if (!nextobj) continue; /* ignore dependencies on undumped objects */ if (findLoop(nextobj, startPoint, workspace, depth, newDepth)) return true; } return false; } /* * 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). * Break the loop and make the I/O 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 I/O 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. */ 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... */ static void repairViewRuleMultiLoop(DumpableObject *viewobj, DumpableObject *ruleobj) { /* remove view's dependency on rule */ removeObjectDependency(viewobj, ruleobj->dumpId); /* pretend view is a plain table and dump it that way */ ((TableInfo *) viewobj)->relkind = 'r'; /* RELKIND_RELATION */ /* mark rule as needing its own dump */ ((RuleInfo *) ruleobj)->separate = true; /* put back rule's dependency on view */ addObjectDependency(ruleobj, viewobj->dumpId); } /* * 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); } /* * 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); } /* * 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 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 and its ON SELECT rule */ if (nLoop == 2 && loop[0]->objType == DO_TABLE && loop[1]->objType == DO_RULE && ((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 && ((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 and ON SELECT rule */ 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_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; } } } } } /* 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 may 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_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_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_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_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; } /* shouldn't get here */ snprintf(buf, bufsize, "object type %d (ID %d OID %u)", (int) obj->objType, obj->dumpId, obj->catId.oid); }