| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * pg_dump_sort.c |
| 4 | * Sort the items of a dump into a safe order for dumping |
| 5 | * |
| 6 | * |
| 7 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 8 | * Portions Copyright (c) 1994, Regents of the University of California |
| 9 | * |
| 10 | * |
| 11 | * IDENTIFICATION |
| 12 | * src/bin/pg_dump/pg_dump_sort.c |
| 13 | * |
| 14 | *------------------------------------------------------------------------- |
| 15 | */ |
| 16 | #include "postgres_fe.h" |
| 17 | |
| 18 | #include "pg_backup_archiver.h" |
| 19 | #include "pg_backup_utils.h" |
| 20 | #include "pg_dump.h" |
| 21 | |
| 22 | #include "catalog/pg_class_d.h" |
| 23 | |
| 24 | /* |
| 25 | * Sort priority for database object types. |
| 26 | * Objects are sorted by type, and within a type by name. |
| 27 | * |
| 28 | * Because materialized views can potentially reference system views, |
| 29 | * DO_REFRESH_MATVIEW should always be the last thing on the list. |
| 30 | * |
| 31 | * NOTE: object-type priorities must match the section assignments made in |
| 32 | * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY, |
| 33 | * POST_DATA objects must sort after DO_POST_DATA_BOUNDARY, and DATA objects |
| 34 | * must sort between them. |
| 35 | */ |
| 36 | static const int dbObjectTypePriority[] = |
| 37 | { |
| 38 | 1, /* DO_NAMESPACE */ |
| 39 | 4, /* DO_EXTENSION */ |
| 40 | 5, /* DO_TYPE */ |
| 41 | 5, /* DO_SHELL_TYPE */ |
| 42 | 6, /* DO_FUNC */ |
| 43 | 7, /* DO_AGG */ |
| 44 | 8, /* DO_OPERATOR */ |
| 45 | 8, /* DO_ACCESS_METHOD */ |
| 46 | 9, /* DO_OPCLASS */ |
| 47 | 9, /* DO_OPFAMILY */ |
| 48 | 3, /* DO_COLLATION */ |
| 49 | 11, /* DO_CONVERSION */ |
| 50 | 18, /* DO_TABLE */ |
| 51 | 20, /* DO_ATTRDEF */ |
| 52 | 28, /* DO_INDEX */ |
| 53 | 29, /* DO_INDEX_ATTACH */ |
| 54 | 30, /* DO_STATSEXT */ |
| 55 | 31, /* DO_RULE */ |
| 56 | 32, /* DO_TRIGGER */ |
| 57 | 27, /* DO_CONSTRAINT */ |
| 58 | 33, /* DO_FK_CONSTRAINT */ |
| 59 | 2, /* DO_PROCLANG */ |
| 60 | 10, /* DO_CAST */ |
| 61 | 23, /* DO_TABLE_DATA */ |
| 62 | 24, /* DO_SEQUENCE_SET */ |
| 63 | 19, /* DO_DUMMY_TYPE */ |
| 64 | 12, /* DO_TSPARSER */ |
| 65 | 14, /* DO_TSDICT */ |
| 66 | 13, /* DO_TSTEMPLATE */ |
| 67 | 15, /* DO_TSCONFIG */ |
| 68 | 16, /* DO_FDW */ |
| 69 | 17, /* DO_FOREIGN_SERVER */ |
| 70 | 33, /* DO_DEFAULT_ACL */ |
| 71 | 3, /* DO_TRANSFORM */ |
| 72 | 21, /* DO_BLOB */ |
| 73 | 25, /* DO_BLOB_DATA */ |
| 74 | 22, /* DO_PRE_DATA_BOUNDARY */ |
| 75 | 26, /* DO_POST_DATA_BOUNDARY */ |
| 76 | 34, /* DO_EVENT_TRIGGER */ |
| 77 | 39, /* DO_REFRESH_MATVIEW */ |
| 78 | 35, /* DO_POLICY */ |
| 79 | 36, /* DO_PUBLICATION */ |
| 80 | 37, /* DO_PUBLICATION_REL */ |
| 81 | 38 /* DO_SUBSCRIPTION */ |
| 82 | }; |
| 83 | |
| 84 | static DumpId preDataBoundId; |
| 85 | static DumpId postDataBoundId; |
| 86 | |
| 87 | |
| 88 | static int DOTypeNameCompare(const void *p1, const void *p2); |
| 89 | static bool TopoSort(DumpableObject **objs, |
| 90 | int numObjs, |
| 91 | DumpableObject **ordering, |
| 92 | int *nOrdering); |
| 93 | static void addHeapElement(int val, int *heap, int heapLength); |
| 94 | static int removeHeapElement(int *heap, int heapLength); |
| 95 | static void findDependencyLoops(DumpableObject **objs, int nObjs, int totObjs); |
| 96 | static int findLoop(DumpableObject *obj, |
| 97 | DumpId startPoint, |
| 98 | bool *processed, |
| 99 | DumpId *searchFailed, |
| 100 | DumpableObject **workspace, |
| 101 | int depth); |
| 102 | static void repairDependencyLoop(DumpableObject **loop, |
| 103 | int nLoop); |
| 104 | static void describeDumpableObject(DumpableObject *obj, |
| 105 | char *buf, int bufsize); |
| 106 | |
| 107 | |
| 108 | /* |
| 109 | * Sort the given objects into a type/name-based ordering |
| 110 | * |
| 111 | * Normally this is just the starting point for the dependency-based |
| 112 | * ordering. |
| 113 | */ |
| 114 | void |
| 115 | sortDumpableObjectsByTypeName(DumpableObject **objs, int numObjs) |
| 116 | { |
| 117 | if (numObjs > 1) |
| 118 | qsort((void *) objs, numObjs, sizeof(DumpableObject *), |
| 119 | DOTypeNameCompare); |
| 120 | } |
| 121 | |
| 122 | static int |
| 123 | DOTypeNameCompare(const void *p1, const void *p2) |
| 124 | { |
| 125 | DumpableObject *obj1 = *(DumpableObject *const *) p1; |
| 126 | DumpableObject *obj2 = *(DumpableObject *const *) p2; |
| 127 | int cmpval; |
| 128 | |
| 129 | /* Sort by type's priority */ |
| 130 | cmpval = dbObjectTypePriority[obj1->objType] - |
| 131 | dbObjectTypePriority[obj2->objType]; |
| 132 | |
| 133 | if (cmpval != 0) |
| 134 | return cmpval; |
| 135 | |
| 136 | /* |
| 137 | * Sort by namespace. Typically, all objects of the same priority would |
| 138 | * either have or not have a namespace link, but there are exceptions. |
| 139 | * Sort NULL namespace after non-NULL in such cases. |
| 140 | */ |
| 141 | if (obj1->namespace) |
| 142 | { |
| 143 | if (obj2->namespace) |
| 144 | { |
| 145 | cmpval = strcmp(obj1->namespace->dobj.name, |
| 146 | obj2->namespace->dobj.name); |
| 147 | if (cmpval != 0) |
| 148 | return cmpval; |
| 149 | } |
| 150 | else |
| 151 | return -1; |
| 152 | } |
| 153 | else if (obj2->namespace) |
| 154 | return 1; |
| 155 | |
| 156 | /* Sort by name */ |
| 157 | cmpval = strcmp(obj1->name, obj2->name); |
| 158 | if (cmpval != 0) |
| 159 | return cmpval; |
| 160 | |
| 161 | /* To have a stable sort order, break ties for some object types */ |
| 162 | if (obj1->objType == DO_FUNC || obj1->objType == DO_AGG) |
| 163 | { |
| 164 | FuncInfo *fobj1 = *(FuncInfo *const *) p1; |
| 165 | FuncInfo *fobj2 = *(FuncInfo *const *) p2; |
| 166 | int i; |
| 167 | |
| 168 | cmpval = fobj1->nargs - fobj2->nargs; |
| 169 | if (cmpval != 0) |
| 170 | return cmpval; |
| 171 | for (i = 0; i < fobj1->nargs; i++) |
| 172 | { |
| 173 | TypeInfo *argtype1 = findTypeByOid(fobj1->argtypes[i]); |
| 174 | TypeInfo *argtype2 = findTypeByOid(fobj2->argtypes[i]); |
| 175 | |
| 176 | if (argtype1 && argtype2) |
| 177 | { |
| 178 | if (argtype1->dobj.namespace && argtype2->dobj.namespace) |
| 179 | { |
| 180 | cmpval = strcmp(argtype1->dobj.namespace->dobj.name, |
| 181 | argtype2->dobj.namespace->dobj.name); |
| 182 | if (cmpval != 0) |
| 183 | return cmpval; |
| 184 | } |
| 185 | cmpval = strcmp(argtype1->dobj.name, argtype2->dobj.name); |
| 186 | if (cmpval != 0) |
| 187 | return cmpval; |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | else if (obj1->objType == DO_OPERATOR) |
| 192 | { |
| 193 | OprInfo *oobj1 = *(OprInfo *const *) p1; |
| 194 | OprInfo *oobj2 = *(OprInfo *const *) p2; |
| 195 | |
| 196 | /* oprkind is 'l', 'r', or 'b'; this sorts prefix, postfix, infix */ |
| 197 | cmpval = (oobj2->oprkind - oobj1->oprkind); |
| 198 | if (cmpval != 0) |
| 199 | return cmpval; |
| 200 | } |
| 201 | else if (obj1->objType == DO_ATTRDEF) |
| 202 | { |
| 203 | AttrDefInfo *adobj1 = *(AttrDefInfo *const *) p1; |
| 204 | AttrDefInfo *adobj2 = *(AttrDefInfo *const *) p2; |
| 205 | |
| 206 | cmpval = (adobj1->adnum - adobj2->adnum); |
| 207 | if (cmpval != 0) |
| 208 | return cmpval; |
| 209 | } |
| 210 | |
| 211 | /* Usually shouldn't get here, but if we do, sort by OID */ |
| 212 | return oidcmp(obj1->catId.oid, obj2->catId.oid); |
| 213 | } |
| 214 | |
| 215 | |
| 216 | /* |
| 217 | * Sort the given objects into a safe dump order using dependency |
| 218 | * information (to the extent we have it available). |
| 219 | * |
| 220 | * The DumpIds of the PRE_DATA_BOUNDARY and POST_DATA_BOUNDARY objects are |
| 221 | * passed in separately, in case we need them during dependency loop repair. |
| 222 | */ |
| 223 | void |
| 224 | sortDumpableObjects(DumpableObject **objs, int numObjs, |
| 225 | DumpId preBoundaryId, DumpId postBoundaryId) |
| 226 | { |
| 227 | DumpableObject **ordering; |
| 228 | int nOrdering; |
| 229 | |
| 230 | if (numObjs <= 0) /* can't happen anymore ... */ |
| 231 | return; |
| 232 | |
| 233 | /* |
| 234 | * Saving the boundary IDs in static variables is a bit grotty, but seems |
| 235 | * better than adding them to parameter lists of subsidiary functions. |
| 236 | */ |
| 237 | preDataBoundId = preBoundaryId; |
| 238 | postDataBoundId = postBoundaryId; |
| 239 | |
| 240 | ordering = (DumpableObject **) pg_malloc(numObjs * sizeof(DumpableObject *)); |
| 241 | while (!TopoSort(objs, numObjs, ordering, &nOrdering)) |
| 242 | findDependencyLoops(ordering, nOrdering, numObjs); |
| 243 | |
| 244 | memcpy(objs, ordering, numObjs * sizeof(DumpableObject *)); |
| 245 | |
| 246 | free(ordering); |
| 247 | } |
| 248 | |
| 249 | /* |
| 250 | * TopoSort -- topological sort of a dump list |
| 251 | * |
| 252 | * Generate a re-ordering of the dump list that satisfies all the dependency |
| 253 | * constraints shown in the dump list. (Each such constraint is a fact of a |
| 254 | * partial ordering.) Minimize rearrangement of the list not needed to |
| 255 | * achieve the partial ordering. |
| 256 | * |
| 257 | * The input is the list of numObjs objects in objs[]. This list is not |
| 258 | * modified. |
| 259 | * |
| 260 | * Returns true if able to build an ordering that satisfies all the |
| 261 | * constraints, false if not (there are contradictory constraints). |
| 262 | * |
| 263 | * On success (true result), ordering[] is filled with a sorted array of |
| 264 | * DumpableObject pointers, of length equal to the input list length. |
| 265 | * |
| 266 | * On failure (false result), ordering[] is filled with an unsorted array of |
| 267 | * DumpableObject pointers of length *nOrdering, listing the objects that |
| 268 | * prevented the sort from being completed. In general, these objects either |
| 269 | * participate directly in a dependency cycle, or are depended on by objects |
| 270 | * that are in a cycle. (The latter objects are not actually problematic, |
| 271 | * but it takes further analysis to identify which are which.) |
| 272 | * |
| 273 | * The caller is responsible for allocating sufficient space at *ordering. |
| 274 | */ |
| 275 | static bool |
| 276 | TopoSort(DumpableObject **objs, |
| 277 | int numObjs, |
| 278 | DumpableObject **ordering, /* output argument */ |
| 279 | int *nOrdering) /* output argument */ |
| 280 | { |
| 281 | DumpId maxDumpId = getMaxDumpId(); |
| 282 | int *pendingHeap; |
| 283 | int *beforeConstraints; |
| 284 | int *idMap; |
| 285 | DumpableObject *obj; |
| 286 | int heapLength; |
| 287 | int i, |
| 288 | j, |
| 289 | k; |
| 290 | |
| 291 | /* |
| 292 | * This is basically the same algorithm shown for topological sorting in |
| 293 | * Knuth's Volume 1. However, we would like to minimize unnecessary |
| 294 | * rearrangement of the input ordering; that is, when we have a choice of |
| 295 | * which item to output next, we always want to take the one highest in |
| 296 | * the original list. Therefore, instead of maintaining an unordered |
| 297 | * linked list of items-ready-to-output as Knuth does, we maintain a heap |
| 298 | * of their item numbers, which we can use as a priority queue. This |
| 299 | * turns the algorithm from O(N) to O(N log N) because each insertion or |
| 300 | * removal of a heap item takes O(log N) time. However, that's still |
| 301 | * plenty fast enough for this application. |
| 302 | */ |
| 303 | |
| 304 | *nOrdering = numObjs; /* for success return */ |
| 305 | |
| 306 | /* Eliminate the null case */ |
| 307 | if (numObjs <= 0) |
| 308 | return true; |
| 309 | |
| 310 | /* Create workspace for the above-described heap */ |
| 311 | pendingHeap = (int *) pg_malloc(numObjs * sizeof(int)); |
| 312 | |
| 313 | /* |
| 314 | * Scan the constraints, and for each item in the input, generate a count |
| 315 | * of the number of constraints that say it must be before something else. |
| 316 | * The count for the item with dumpId j is stored in beforeConstraints[j]. |
| 317 | * We also make a map showing the input-order index of the item with |
| 318 | * dumpId j. |
| 319 | */ |
| 320 | beforeConstraints = (int *) pg_malloc0((maxDumpId + 1) * sizeof(int)); |
| 321 | idMap = (int *) pg_malloc((maxDumpId + 1) * sizeof(int)); |
| 322 | for (i = 0; i < numObjs; i++) |
| 323 | { |
| 324 | obj = objs[i]; |
| 325 | j = obj->dumpId; |
| 326 | if (j <= 0 || j > maxDumpId) |
| 327 | fatal("invalid dumpId %d" , j); |
| 328 | idMap[j] = i; |
| 329 | for (j = 0; j < obj->nDeps; j++) |
| 330 | { |
| 331 | k = obj->dependencies[j]; |
| 332 | if (k <= 0 || k > maxDumpId) |
| 333 | fatal("invalid dependency %d" , k); |
| 334 | beforeConstraints[k]++; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | /* |
| 339 | * Now initialize the heap of items-ready-to-output by filling it with the |
| 340 | * indexes of items that already have beforeConstraints[id] == 0. |
| 341 | * |
| 342 | * The essential property of a heap is heap[(j-1)/2] >= heap[j] for each j |
| 343 | * in the range 1..heapLength-1 (note we are using 0-based subscripts |
| 344 | * here, while the discussion in Knuth assumes 1-based subscripts). So, if |
| 345 | * we simply enter the indexes into pendingHeap[] in decreasing order, we |
| 346 | * a-fortiori have the heap invariant satisfied at completion of this |
| 347 | * loop, and don't need to do any sift-up comparisons. |
| 348 | */ |
| 349 | heapLength = 0; |
| 350 | for (i = numObjs; --i >= 0;) |
| 351 | { |
| 352 | if (beforeConstraints[objs[i]->dumpId] == 0) |
| 353 | pendingHeap[heapLength++] = i; |
| 354 | } |
| 355 | |
| 356 | /*-------------------- |
| 357 | * Now emit objects, working backwards in the output list. At each step, |
| 358 | * we use the priority heap to select the last item that has no remaining |
| 359 | * before-constraints. We remove that item from the heap, output it to |
| 360 | * ordering[], and decrease the beforeConstraints count of each of the |
| 361 | * items it was constrained against. Whenever an item's beforeConstraints |
| 362 | * count is thereby decreased to zero, we insert it into the priority heap |
| 363 | * to show that it is a candidate to output. We are done when the heap |
| 364 | * becomes empty; if we have output every element then we succeeded, |
| 365 | * otherwise we failed. |
| 366 | * i = number of ordering[] entries left to output |
| 367 | * j = objs[] index of item we are outputting |
| 368 | * k = temp for scanning constraint list for item j |
| 369 | *-------------------- |
| 370 | */ |
| 371 | i = numObjs; |
| 372 | while (heapLength > 0) |
| 373 | { |
| 374 | /* Select object to output by removing largest heap member */ |
| 375 | j = removeHeapElement(pendingHeap, heapLength--); |
| 376 | obj = objs[j]; |
| 377 | /* Output candidate to ordering[] */ |
| 378 | ordering[--i] = obj; |
| 379 | /* Update beforeConstraints counts of its predecessors */ |
| 380 | for (k = 0; k < obj->nDeps; k++) |
| 381 | { |
| 382 | int id = obj->dependencies[k]; |
| 383 | |
| 384 | if ((--beforeConstraints[id]) == 0) |
| 385 | addHeapElement(idMap[id], pendingHeap, heapLength++); |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | /* |
| 390 | * If we failed, report the objects that couldn't be output; these are the |
| 391 | * ones with beforeConstraints[] still nonzero. |
| 392 | */ |
| 393 | if (i != 0) |
| 394 | { |
| 395 | k = 0; |
| 396 | for (j = 1; j <= maxDumpId; j++) |
| 397 | { |
| 398 | if (beforeConstraints[j] != 0) |
| 399 | ordering[k++] = objs[idMap[j]]; |
| 400 | } |
| 401 | *nOrdering = k; |
| 402 | } |
| 403 | |
| 404 | /* Done */ |
| 405 | free(pendingHeap); |
| 406 | free(beforeConstraints); |
| 407 | free(idMap); |
| 408 | |
| 409 | return (i == 0); |
| 410 | } |
| 411 | |
| 412 | /* |
| 413 | * Add an item to a heap (priority queue) |
| 414 | * |
| 415 | * heapLength is the current heap size; caller is responsible for increasing |
| 416 | * its value after the call. There must be sufficient storage at *heap. |
| 417 | */ |
| 418 | static void |
| 419 | addHeapElement(int val, int *heap, int heapLength) |
| 420 | { |
| 421 | int j; |
| 422 | |
| 423 | /* |
| 424 | * Sift-up the new entry, per Knuth 5.2.3 exercise 16. Note that Knuth is |
| 425 | * using 1-based array indexes, not 0-based. |
| 426 | */ |
| 427 | j = heapLength; |
| 428 | while (j > 0) |
| 429 | { |
| 430 | int i = (j - 1) >> 1; |
| 431 | |
| 432 | if (val <= heap[i]) |
| 433 | break; |
| 434 | heap[j] = heap[i]; |
| 435 | j = i; |
| 436 | } |
| 437 | heap[j] = val; |
| 438 | } |
| 439 | |
| 440 | /* |
| 441 | * Remove the largest item present in a heap (priority queue) |
| 442 | * |
| 443 | * heapLength is the current heap size; caller is responsible for decreasing |
| 444 | * its value after the call. |
| 445 | * |
| 446 | * We remove and return heap[0], which is always the largest element of |
| 447 | * the heap, and then "sift up" to maintain the heap invariant. |
| 448 | */ |
| 449 | static int |
| 450 | removeHeapElement(int *heap, int heapLength) |
| 451 | { |
| 452 | int result = heap[0]; |
| 453 | int val; |
| 454 | int i; |
| 455 | |
| 456 | if (--heapLength <= 0) |
| 457 | return result; |
| 458 | val = heap[heapLength]; /* value that must be reinserted */ |
| 459 | i = 0; /* i is where the "hole" is */ |
| 460 | for (;;) |
| 461 | { |
| 462 | int j = 2 * i + 1; |
| 463 | |
| 464 | if (j >= heapLength) |
| 465 | break; |
| 466 | if (j + 1 < heapLength && |
| 467 | heap[j] < heap[j + 1]) |
| 468 | j++; |
| 469 | if (val >= heap[j]) |
| 470 | break; |
| 471 | heap[i] = heap[j]; |
| 472 | i = j; |
| 473 | } |
| 474 | heap[i] = val; |
| 475 | return result; |
| 476 | } |
| 477 | |
| 478 | /* |
| 479 | * findDependencyLoops - identify loops in TopoSort's failure output, |
| 480 | * and pass each such loop to repairDependencyLoop() for action |
| 481 | * |
| 482 | * In general there may be many loops in the set of objects returned by |
| 483 | * TopoSort; for speed we should try to repair as many loops as we can |
| 484 | * before trying TopoSort again. We can safely repair loops that are |
| 485 | * disjoint (have no members in common); if we find overlapping loops |
| 486 | * then we repair only the first one found, because the action taken to |
| 487 | * repair the first might have repaired the other as well. (If not, |
| 488 | * we'll fix it on the next go-round.) |
| 489 | * |
| 490 | * objs[] lists the objects TopoSort couldn't sort |
| 491 | * nObjs is the number of such objects |
| 492 | * totObjs is the total number of objects in the universe |
| 493 | */ |
| 494 | static void |
| 495 | findDependencyLoops(DumpableObject **objs, int nObjs, int totObjs) |
| 496 | { |
| 497 | /* |
| 498 | * We use three data structures here: |
| 499 | * |
| 500 | * processed[] is a bool array indexed by dump ID, marking the objects |
| 501 | * already processed during this invocation of findDependencyLoops(). |
| 502 | * |
| 503 | * searchFailed[] is another array indexed by dump ID. searchFailed[j] is |
| 504 | * set to dump ID k if we have proven that there is no dependency path |
| 505 | * leading from object j back to start point k. This allows us to skip |
| 506 | * useless searching when there are multiple dependency paths from k to j, |
| 507 | * which is a common situation. We could use a simple bool array for |
| 508 | * this, but then we'd need to re-zero it for each start point, resulting |
| 509 | * in O(N^2) zeroing work. Using the start point's dump ID as the "true" |
| 510 | * value lets us skip clearing the array before we consider the next start |
| 511 | * point. |
| 512 | * |
| 513 | * workspace[] is an array of DumpableObject pointers, in which we try to |
| 514 | * build lists of objects constituting loops. We make workspace[] large |
| 515 | * enough to hold all the objects in TopoSort's output, which is huge |
| 516 | * overkill in most cases but could theoretically be necessary if there is |
| 517 | * a single dependency chain linking all the objects. |
| 518 | */ |
| 519 | bool *processed; |
| 520 | DumpId *searchFailed; |
| 521 | DumpableObject **workspace; |
| 522 | bool fixedloop; |
| 523 | int i; |
| 524 | |
| 525 | processed = (bool *) pg_malloc0((getMaxDumpId() + 1) * sizeof(bool)); |
| 526 | searchFailed = (DumpId *) pg_malloc0((getMaxDumpId() + 1) * sizeof(DumpId)); |
| 527 | workspace = (DumpableObject **) pg_malloc(totObjs * sizeof(DumpableObject *)); |
| 528 | fixedloop = false; |
| 529 | |
| 530 | for (i = 0; i < nObjs; i++) |
| 531 | { |
| 532 | DumpableObject *obj = objs[i]; |
| 533 | int looplen; |
| 534 | int j; |
| 535 | |
| 536 | looplen = findLoop(obj, |
| 537 | obj->dumpId, |
| 538 | processed, |
| 539 | searchFailed, |
| 540 | workspace, |
| 541 | 0); |
| 542 | |
| 543 | if (looplen > 0) |
| 544 | { |
| 545 | /* Found a loop, repair it */ |
| 546 | repairDependencyLoop(workspace, looplen); |
| 547 | fixedloop = true; |
| 548 | /* Mark loop members as processed */ |
| 549 | for (j = 0; j < looplen; j++) |
| 550 | processed[workspace[j]->dumpId] = true; |
| 551 | } |
| 552 | else |
| 553 | { |
| 554 | /* |
| 555 | * There's no loop starting at this object, but mark it processed |
| 556 | * anyway. This is not necessary for correctness, but saves later |
| 557 | * invocations of findLoop() from uselessly chasing references to |
| 558 | * such an object. |
| 559 | */ |
| 560 | processed[obj->dumpId] = true; |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | /* We'd better have fixed at least one loop */ |
| 565 | if (!fixedloop) |
| 566 | fatal("could not identify dependency loop" ); |
| 567 | |
| 568 | free(workspace); |
| 569 | free(searchFailed); |
| 570 | free(processed); |
| 571 | } |
| 572 | |
| 573 | /* |
| 574 | * Recursively search for a circular dependency loop that doesn't include |
| 575 | * any already-processed objects. |
| 576 | * |
| 577 | * obj: object we are examining now |
| 578 | * startPoint: dumpId of starting object for the hoped-for circular loop |
| 579 | * processed[]: flag array marking already-processed objects |
| 580 | * searchFailed[]: flag array marking already-unsuccessfully-visited objects |
| 581 | * workspace[]: work array in which we are building list of loop members |
| 582 | * depth: number of valid entries in workspace[] at call |
| 583 | * |
| 584 | * On success, the length of the loop is returned, and workspace[] is filled |
| 585 | * with pointers to the members of the loop. On failure, we return 0. |
| 586 | * |
| 587 | * Note: it is possible that the given starting object is a member of more |
| 588 | * than one cycle; if so, we will find an arbitrary one of the cycles. |
| 589 | */ |
| 590 | static int |
| 591 | findLoop(DumpableObject *obj, |
| 592 | DumpId startPoint, |
| 593 | bool *processed, |
| 594 | DumpId *searchFailed, |
| 595 | DumpableObject **workspace, |
| 596 | int depth) |
| 597 | { |
| 598 | int i; |
| 599 | |
| 600 | /* |
| 601 | * Reject if obj is already processed. This test prevents us from finding |
| 602 | * loops that overlap previously-processed loops. |
| 603 | */ |
| 604 | if (processed[obj->dumpId]) |
| 605 | return 0; |
| 606 | |
| 607 | /* |
| 608 | * If we've already proven there is no path from this object back to the |
| 609 | * startPoint, forget it. |
| 610 | */ |
| 611 | if (searchFailed[obj->dumpId] == startPoint) |
| 612 | return 0; |
| 613 | |
| 614 | /* |
| 615 | * Reject if obj is already present in workspace. This test prevents us |
| 616 | * from going into infinite recursion if we are given a startPoint object |
| 617 | * that links to a cycle it's not a member of, and it guarantees that we |
| 618 | * can't overflow the allocated size of workspace[]. |
| 619 | */ |
| 620 | for (i = 0; i < depth; i++) |
| 621 | { |
| 622 | if (workspace[i] == obj) |
| 623 | return 0; |
| 624 | } |
| 625 | |
| 626 | /* |
| 627 | * Okay, tentatively add obj to workspace |
| 628 | */ |
| 629 | workspace[depth++] = obj; |
| 630 | |
| 631 | /* |
| 632 | * See if we've found a loop back to the desired startPoint; if so, done |
| 633 | */ |
| 634 | for (i = 0; i < obj->nDeps; i++) |
| 635 | { |
| 636 | if (obj->dependencies[i] == startPoint) |
| 637 | return depth; |
| 638 | } |
| 639 | |
| 640 | /* |
| 641 | * Recurse down each outgoing branch |
| 642 | */ |
| 643 | for (i = 0; i < obj->nDeps; i++) |
| 644 | { |
| 645 | DumpableObject *nextobj = findObjectByDumpId(obj->dependencies[i]); |
| 646 | int newDepth; |
| 647 | |
| 648 | if (!nextobj) |
| 649 | continue; /* ignore dependencies on undumped objects */ |
| 650 | newDepth = findLoop(nextobj, |
| 651 | startPoint, |
| 652 | processed, |
| 653 | searchFailed, |
| 654 | workspace, |
| 655 | depth); |
| 656 | if (newDepth > 0) |
| 657 | return newDepth; |
| 658 | } |
| 659 | |
| 660 | /* |
| 661 | * Remember there is no path from here back to startPoint |
| 662 | */ |
| 663 | searchFailed[obj->dumpId] = startPoint; |
| 664 | |
| 665 | return 0; |
| 666 | } |
| 667 | |
| 668 | /* |
| 669 | * A user-defined datatype will have a dependency loop with each of its |
| 670 | * I/O functions (since those have the datatype as input or output). |
| 671 | * Similarly, a range type will have a loop with its canonicalize function, |
| 672 | * if any. Break the loop by making the function depend on the associated |
| 673 | * shell type, instead. |
| 674 | */ |
| 675 | static void |
| 676 | repairTypeFuncLoop(DumpableObject *typeobj, DumpableObject *funcobj) |
| 677 | { |
| 678 | TypeInfo *typeInfo = (TypeInfo *) typeobj; |
| 679 | |
| 680 | /* remove function's dependency on type */ |
| 681 | removeObjectDependency(funcobj, typeobj->dumpId); |
| 682 | |
| 683 | /* add function's dependency on shell type, instead */ |
| 684 | if (typeInfo->shellType) |
| 685 | { |
| 686 | addObjectDependency(funcobj, typeInfo->shellType->dobj.dumpId); |
| 687 | |
| 688 | /* |
| 689 | * Mark shell type (always including the definition, as we need the |
| 690 | * shell type defined to identify the function fully) as to be dumped |
| 691 | * if any such function is |
| 692 | */ |
| 693 | if (funcobj->dump) |
| 694 | typeInfo->shellType->dobj.dump = funcobj->dump | |
| 695 | DUMP_COMPONENT_DEFINITION; |
| 696 | } |
| 697 | } |
| 698 | |
| 699 | /* |
| 700 | * Because we force a view to depend on its ON SELECT rule, while there |
| 701 | * will be an implicit dependency in the other direction, we need to break |
| 702 | * the loop. If there are no other objects in the loop then we can remove |
| 703 | * the implicit dependency and leave the ON SELECT rule non-separate. |
| 704 | * This applies to matviews, as well. |
| 705 | */ |
| 706 | static void |
| 707 | repairViewRuleLoop(DumpableObject *viewobj, |
| 708 | DumpableObject *ruleobj) |
| 709 | { |
| 710 | /* remove rule's dependency on view */ |
| 711 | removeObjectDependency(ruleobj, viewobj->dumpId); |
| 712 | /* flags on the two objects are already set correctly for this case */ |
| 713 | } |
| 714 | |
| 715 | /* |
| 716 | * However, if there are other objects in the loop, we must break the loop |
| 717 | * by making the ON SELECT rule a separately-dumped object. |
| 718 | * |
| 719 | * Because findLoop() finds shorter cycles before longer ones, it's likely |
| 720 | * that we will have previously fired repairViewRuleLoop() and removed the |
| 721 | * rule's dependency on the view. Put it back to ensure the rule won't be |
| 722 | * emitted before the view. |
| 723 | * |
| 724 | * Note: this approach does *not* work for matviews, at the moment. |
| 725 | */ |
| 726 | static void |
| 727 | repairViewRuleMultiLoop(DumpableObject *viewobj, |
| 728 | DumpableObject *ruleobj) |
| 729 | { |
| 730 | TableInfo *viewinfo = (TableInfo *) viewobj; |
| 731 | RuleInfo *ruleinfo = (RuleInfo *) ruleobj; |
| 732 | |
| 733 | /* remove view's dependency on rule */ |
| 734 | removeObjectDependency(viewobj, ruleobj->dumpId); |
| 735 | /* mark view to be printed with a dummy definition */ |
| 736 | viewinfo->dummy_view = true; |
| 737 | /* mark rule as needing its own dump */ |
| 738 | ruleinfo->separate = true; |
| 739 | /* put back rule's dependency on view */ |
| 740 | addObjectDependency(ruleobj, viewobj->dumpId); |
| 741 | /* now that rule is separate, it must be post-data */ |
| 742 | addObjectDependency(ruleobj, postDataBoundId); |
| 743 | } |
| 744 | |
| 745 | /* |
| 746 | * If a matview is involved in a multi-object loop, we can't currently fix |
| 747 | * that by splitting off the rule. As a stopgap, we try to fix it by |
| 748 | * dropping the constraint that the matview be dumped in the pre-data section. |
| 749 | * This is sufficient to handle cases where a matview depends on some unique |
| 750 | * index, as can happen if it has a GROUP BY for example. |
| 751 | * |
| 752 | * Note that the "next object" is not necessarily the matview itself; |
| 753 | * it could be the matview's rowtype, for example. We may come through here |
| 754 | * several times while removing all the pre-data linkages. In particular, |
| 755 | * if there are other matviews that depend on the one with the circularity |
| 756 | * problem, we'll come through here for each such matview and mark them all |
| 757 | * as postponed. (This works because all MVs have pre-data dependencies |
| 758 | * to begin with, so each of them will get visited.) |
| 759 | */ |
| 760 | static void |
| 761 | repairMatViewBoundaryMultiLoop(DumpableObject *boundaryobj, |
| 762 | DumpableObject *nextobj) |
| 763 | { |
| 764 | /* remove boundary's dependency on object after it in loop */ |
| 765 | removeObjectDependency(boundaryobj, nextobj->dumpId); |
| 766 | /* if that object is a matview, mark it as postponed into post-data */ |
| 767 | if (nextobj->objType == DO_TABLE) |
| 768 | { |
| 769 | TableInfo *nextinfo = (TableInfo *) nextobj; |
| 770 | |
| 771 | if (nextinfo->relkind == RELKIND_MATVIEW) |
| 772 | nextinfo->postponed_def = true; |
| 773 | } |
| 774 | } |
| 775 | |
| 776 | /* |
| 777 | * Because we make tables depend on their CHECK constraints, while there |
| 778 | * will be an automatic dependency in the other direction, we need to break |
| 779 | * the loop. If there are no other objects in the loop then we can remove |
| 780 | * the automatic dependency and leave the CHECK constraint non-separate. |
| 781 | */ |
| 782 | static void |
| 783 | repairTableConstraintLoop(DumpableObject *tableobj, |
| 784 | DumpableObject *constraintobj) |
| 785 | { |
| 786 | /* remove constraint's dependency on table */ |
| 787 | removeObjectDependency(constraintobj, tableobj->dumpId); |
| 788 | } |
| 789 | |
| 790 | /* |
| 791 | * However, if there are other objects in the loop, we must break the loop |
| 792 | * by making the CHECK constraint a separately-dumped object. |
| 793 | * |
| 794 | * Because findLoop() finds shorter cycles before longer ones, it's likely |
| 795 | * that we will have previously fired repairTableConstraintLoop() and |
| 796 | * removed the constraint's dependency on the table. Put it back to ensure |
| 797 | * the constraint won't be emitted before the table... |
| 798 | */ |
| 799 | static void |
| 800 | repairTableConstraintMultiLoop(DumpableObject *tableobj, |
| 801 | DumpableObject *constraintobj) |
| 802 | { |
| 803 | /* remove table's dependency on constraint */ |
| 804 | removeObjectDependency(tableobj, constraintobj->dumpId); |
| 805 | /* mark constraint as needing its own dump */ |
| 806 | ((ConstraintInfo *) constraintobj)->separate = true; |
| 807 | /* put back constraint's dependency on table */ |
| 808 | addObjectDependency(constraintobj, tableobj->dumpId); |
| 809 | /* now that constraint is separate, it must be post-data */ |
| 810 | addObjectDependency(constraintobj, postDataBoundId); |
| 811 | } |
| 812 | |
| 813 | /* |
| 814 | * Attribute defaults behave exactly the same as CHECK constraints... |
| 815 | */ |
| 816 | static void |
| 817 | repairTableAttrDefLoop(DumpableObject *tableobj, |
| 818 | DumpableObject *attrdefobj) |
| 819 | { |
| 820 | /* remove attrdef's dependency on table */ |
| 821 | removeObjectDependency(attrdefobj, tableobj->dumpId); |
| 822 | } |
| 823 | |
| 824 | static void |
| 825 | repairTableAttrDefMultiLoop(DumpableObject *tableobj, |
| 826 | DumpableObject *attrdefobj) |
| 827 | { |
| 828 | /* remove table's dependency on attrdef */ |
| 829 | removeObjectDependency(tableobj, attrdefobj->dumpId); |
| 830 | /* mark attrdef as needing its own dump */ |
| 831 | ((AttrDefInfo *) attrdefobj)->separate = true; |
| 832 | /* put back attrdef's dependency on table */ |
| 833 | addObjectDependency(attrdefobj, tableobj->dumpId); |
| 834 | } |
| 835 | |
| 836 | /* |
| 837 | * CHECK constraints on domains work just like those on tables ... |
| 838 | */ |
| 839 | static void |
| 840 | repairDomainConstraintLoop(DumpableObject *domainobj, |
| 841 | DumpableObject *constraintobj) |
| 842 | { |
| 843 | /* remove constraint's dependency on domain */ |
| 844 | removeObjectDependency(constraintobj, domainobj->dumpId); |
| 845 | } |
| 846 | |
| 847 | static void |
| 848 | repairDomainConstraintMultiLoop(DumpableObject *domainobj, |
| 849 | DumpableObject *constraintobj) |
| 850 | { |
| 851 | /* remove domain's dependency on constraint */ |
| 852 | removeObjectDependency(domainobj, constraintobj->dumpId); |
| 853 | /* mark constraint as needing its own dump */ |
| 854 | ((ConstraintInfo *) constraintobj)->separate = true; |
| 855 | /* put back constraint's dependency on domain */ |
| 856 | addObjectDependency(constraintobj, domainobj->dumpId); |
| 857 | /* now that constraint is separate, it must be post-data */ |
| 858 | addObjectDependency(constraintobj, postDataBoundId); |
| 859 | } |
| 860 | |
| 861 | static void |
| 862 | repairIndexLoop(DumpableObject *partedindex, |
| 863 | DumpableObject *partindex) |
| 864 | { |
| 865 | removeObjectDependency(partedindex, partindex->dumpId); |
| 866 | } |
| 867 | |
| 868 | /* |
| 869 | * Fix a dependency loop, or die trying ... |
| 870 | * |
| 871 | * This routine is mainly concerned with reducing the multiple ways that |
| 872 | * a loop might appear to common cases, which it passes off to the |
| 873 | * "fixer" routines above. |
| 874 | */ |
| 875 | static void |
| 876 | repairDependencyLoop(DumpableObject **loop, |
| 877 | int nLoop) |
| 878 | { |
| 879 | int i, |
| 880 | j; |
| 881 | |
| 882 | /* Datatype and one of its I/O or canonicalize functions */ |
| 883 | if (nLoop == 2 && |
| 884 | loop[0]->objType == DO_TYPE && |
| 885 | loop[1]->objType == DO_FUNC) |
| 886 | { |
| 887 | repairTypeFuncLoop(loop[0], loop[1]); |
| 888 | return; |
| 889 | } |
| 890 | if (nLoop == 2 && |
| 891 | loop[1]->objType == DO_TYPE && |
| 892 | loop[0]->objType == DO_FUNC) |
| 893 | { |
| 894 | repairTypeFuncLoop(loop[1], loop[0]); |
| 895 | return; |
| 896 | } |
| 897 | |
| 898 | /* View (including matview) and its ON SELECT rule */ |
| 899 | if (nLoop == 2 && |
| 900 | loop[0]->objType == DO_TABLE && |
| 901 | loop[1]->objType == DO_RULE && |
| 902 | (((TableInfo *) loop[0])->relkind == RELKIND_VIEW || |
| 903 | ((TableInfo *) loop[0])->relkind == RELKIND_MATVIEW) && |
| 904 | ((RuleInfo *) loop[1])->ev_type == '1' && |
| 905 | ((RuleInfo *) loop[1])->is_instead && |
| 906 | ((RuleInfo *) loop[1])->ruletable == (TableInfo *) loop[0]) |
| 907 | { |
| 908 | repairViewRuleLoop(loop[0], loop[1]); |
| 909 | return; |
| 910 | } |
| 911 | if (nLoop == 2 && |
| 912 | loop[1]->objType == DO_TABLE && |
| 913 | loop[0]->objType == DO_RULE && |
| 914 | (((TableInfo *) loop[1])->relkind == RELKIND_VIEW || |
| 915 | ((TableInfo *) loop[1])->relkind == RELKIND_MATVIEW) && |
| 916 | ((RuleInfo *) loop[0])->ev_type == '1' && |
| 917 | ((RuleInfo *) loop[0])->is_instead && |
| 918 | ((RuleInfo *) loop[0])->ruletable == (TableInfo *) loop[1]) |
| 919 | { |
| 920 | repairViewRuleLoop(loop[1], loop[0]); |
| 921 | return; |
| 922 | } |
| 923 | |
| 924 | /* Indirect loop involving view (but not matview) and ON SELECT rule */ |
| 925 | if (nLoop > 2) |
| 926 | { |
| 927 | for (i = 0; i < nLoop; i++) |
| 928 | { |
| 929 | if (loop[i]->objType == DO_TABLE && |
| 930 | ((TableInfo *) loop[i])->relkind == RELKIND_VIEW) |
| 931 | { |
| 932 | for (j = 0; j < nLoop; j++) |
| 933 | { |
| 934 | if (loop[j]->objType == DO_RULE && |
| 935 | ((RuleInfo *) loop[j])->ev_type == '1' && |
| 936 | ((RuleInfo *) loop[j])->is_instead && |
| 937 | ((RuleInfo *) loop[j])->ruletable == (TableInfo *) loop[i]) |
| 938 | { |
| 939 | repairViewRuleMultiLoop(loop[i], loop[j]); |
| 940 | return; |
| 941 | } |
| 942 | } |
| 943 | } |
| 944 | } |
| 945 | } |
| 946 | |
| 947 | /* Indirect loop involving matview and data boundary */ |
| 948 | if (nLoop > 2) |
| 949 | { |
| 950 | for (i = 0; i < nLoop; i++) |
| 951 | { |
| 952 | if (loop[i]->objType == DO_TABLE && |
| 953 | ((TableInfo *) loop[i])->relkind == RELKIND_MATVIEW) |
| 954 | { |
| 955 | for (j = 0; j < nLoop; j++) |
| 956 | { |
| 957 | if (loop[j]->objType == DO_PRE_DATA_BOUNDARY) |
| 958 | { |
| 959 | DumpableObject *nextobj; |
| 960 | |
| 961 | nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0]; |
| 962 | repairMatViewBoundaryMultiLoop(loop[j], nextobj); |
| 963 | return; |
| 964 | } |
| 965 | } |
| 966 | } |
| 967 | } |
| 968 | } |
| 969 | |
| 970 | /* Table and CHECK constraint */ |
| 971 | if (nLoop == 2 && |
| 972 | loop[0]->objType == DO_TABLE && |
| 973 | loop[1]->objType == DO_CONSTRAINT && |
| 974 | ((ConstraintInfo *) loop[1])->contype == 'c' && |
| 975 | ((ConstraintInfo *) loop[1])->contable == (TableInfo *) loop[0]) |
| 976 | { |
| 977 | repairTableConstraintLoop(loop[0], loop[1]); |
| 978 | return; |
| 979 | } |
| 980 | if (nLoop == 2 && |
| 981 | loop[1]->objType == DO_TABLE && |
| 982 | loop[0]->objType == DO_CONSTRAINT && |
| 983 | ((ConstraintInfo *) loop[0])->contype == 'c' && |
| 984 | ((ConstraintInfo *) loop[0])->contable == (TableInfo *) loop[1]) |
| 985 | { |
| 986 | repairTableConstraintLoop(loop[1], loop[0]); |
| 987 | return; |
| 988 | } |
| 989 | |
| 990 | /* Indirect loop involving table and CHECK constraint */ |
| 991 | if (nLoop > 2) |
| 992 | { |
| 993 | for (i = 0; i < nLoop; i++) |
| 994 | { |
| 995 | if (loop[i]->objType == DO_TABLE) |
| 996 | { |
| 997 | for (j = 0; j < nLoop; j++) |
| 998 | { |
| 999 | if (loop[j]->objType == DO_CONSTRAINT && |
| 1000 | ((ConstraintInfo *) loop[j])->contype == 'c' && |
| 1001 | ((ConstraintInfo *) loop[j])->contable == (TableInfo *) loop[i]) |
| 1002 | { |
| 1003 | repairTableConstraintMultiLoop(loop[i], loop[j]); |
| 1004 | return; |
| 1005 | } |
| 1006 | } |
| 1007 | } |
| 1008 | } |
| 1009 | } |
| 1010 | |
| 1011 | /* Table and attribute default */ |
| 1012 | if (nLoop == 2 && |
| 1013 | loop[0]->objType == DO_TABLE && |
| 1014 | loop[1]->objType == DO_ATTRDEF && |
| 1015 | ((AttrDefInfo *) loop[1])->adtable == (TableInfo *) loop[0]) |
| 1016 | { |
| 1017 | repairTableAttrDefLoop(loop[0], loop[1]); |
| 1018 | return; |
| 1019 | } |
| 1020 | if (nLoop == 2 && |
| 1021 | loop[1]->objType == DO_TABLE && |
| 1022 | loop[0]->objType == DO_ATTRDEF && |
| 1023 | ((AttrDefInfo *) loop[0])->adtable == (TableInfo *) loop[1]) |
| 1024 | { |
| 1025 | repairTableAttrDefLoop(loop[1], loop[0]); |
| 1026 | return; |
| 1027 | } |
| 1028 | |
| 1029 | /* index on partitioned table and corresponding index on partition */ |
| 1030 | if (nLoop == 2 && |
| 1031 | loop[0]->objType == DO_INDEX && |
| 1032 | loop[1]->objType == DO_INDEX) |
| 1033 | { |
| 1034 | if (((IndxInfo *) loop[0])->parentidx == loop[1]->catId.oid) |
| 1035 | { |
| 1036 | repairIndexLoop(loop[0], loop[1]); |
| 1037 | return; |
| 1038 | } |
| 1039 | else if (((IndxInfo *) loop[1])->parentidx == loop[0]->catId.oid) |
| 1040 | { |
| 1041 | repairIndexLoop(loop[1], loop[0]); |
| 1042 | return; |
| 1043 | } |
| 1044 | } |
| 1045 | |
| 1046 | /* Indirect loop involving table and attribute default */ |
| 1047 | if (nLoop > 2) |
| 1048 | { |
| 1049 | for (i = 0; i < nLoop; i++) |
| 1050 | { |
| 1051 | if (loop[i]->objType == DO_TABLE) |
| 1052 | { |
| 1053 | for (j = 0; j < nLoop; j++) |
| 1054 | { |
| 1055 | if (loop[j]->objType == DO_ATTRDEF && |
| 1056 | ((AttrDefInfo *) loop[j])->adtable == (TableInfo *) loop[i]) |
| 1057 | { |
| 1058 | repairTableAttrDefMultiLoop(loop[i], loop[j]); |
| 1059 | return; |
| 1060 | } |
| 1061 | } |
| 1062 | } |
| 1063 | } |
| 1064 | } |
| 1065 | |
| 1066 | /* Domain and CHECK constraint */ |
| 1067 | if (nLoop == 2 && |
| 1068 | loop[0]->objType == DO_TYPE && |
| 1069 | loop[1]->objType == DO_CONSTRAINT && |
| 1070 | ((ConstraintInfo *) loop[1])->contype == 'c' && |
| 1071 | ((ConstraintInfo *) loop[1])->condomain == (TypeInfo *) loop[0]) |
| 1072 | { |
| 1073 | repairDomainConstraintLoop(loop[0], loop[1]); |
| 1074 | return; |
| 1075 | } |
| 1076 | if (nLoop == 2 && |
| 1077 | loop[1]->objType == DO_TYPE && |
| 1078 | loop[0]->objType == DO_CONSTRAINT && |
| 1079 | ((ConstraintInfo *) loop[0])->contype == 'c' && |
| 1080 | ((ConstraintInfo *) loop[0])->condomain == (TypeInfo *) loop[1]) |
| 1081 | { |
| 1082 | repairDomainConstraintLoop(loop[1], loop[0]); |
| 1083 | return; |
| 1084 | } |
| 1085 | |
| 1086 | /* Indirect loop involving domain and CHECK constraint */ |
| 1087 | if (nLoop > 2) |
| 1088 | { |
| 1089 | for (i = 0; i < nLoop; i++) |
| 1090 | { |
| 1091 | if (loop[i]->objType == DO_TYPE) |
| 1092 | { |
| 1093 | for (j = 0; j < nLoop; j++) |
| 1094 | { |
| 1095 | if (loop[j]->objType == DO_CONSTRAINT && |
| 1096 | ((ConstraintInfo *) loop[j])->contype == 'c' && |
| 1097 | ((ConstraintInfo *) loop[j])->condomain == (TypeInfo *) loop[i]) |
| 1098 | { |
| 1099 | repairDomainConstraintMultiLoop(loop[i], loop[j]); |
| 1100 | return; |
| 1101 | } |
| 1102 | } |
| 1103 | } |
| 1104 | } |
| 1105 | } |
| 1106 | |
| 1107 | /* |
| 1108 | * Loop of table with itself --- just ignore it. |
| 1109 | * |
| 1110 | * (Actually, what this arises from is a dependency of a table column on |
| 1111 | * another column, which happens with generated columns; or a dependency |
| 1112 | * of a table column on the whole table, which happens with partitioning. |
| 1113 | * But we didn't pay attention to sub-object IDs while collecting the |
| 1114 | * dependency data, so we can't see that here.) |
| 1115 | */ |
| 1116 | if (nLoop == 1) |
| 1117 | { |
| 1118 | if (loop[0]->objType == DO_TABLE) |
| 1119 | { |
| 1120 | removeObjectDependency(loop[0], loop[0]->dumpId); |
| 1121 | return; |
| 1122 | } |
| 1123 | } |
| 1124 | |
| 1125 | /* |
| 1126 | * If all the objects are TABLE_DATA items, what we must have is a |
| 1127 | * circular set of foreign key constraints (or a single self-referential |
| 1128 | * table). Print an appropriate complaint and break the loop arbitrarily. |
| 1129 | */ |
| 1130 | for (i = 0; i < nLoop; i++) |
| 1131 | { |
| 1132 | if (loop[i]->objType != DO_TABLE_DATA) |
| 1133 | break; |
| 1134 | } |
| 1135 | if (i >= nLoop) |
| 1136 | { |
| 1137 | pg_log_warning(ngettext("there are circular foreign-key constraints on this table:" , |
| 1138 | "there are circular foreign-key constraints among these tables:" , |
| 1139 | nLoop)); |
| 1140 | for (i = 0; i < nLoop; i++) |
| 1141 | pg_log_generic(PG_LOG_INFO, " %s" , loop[i]->name); |
| 1142 | pg_log_generic(PG_LOG_INFO, "You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints." ); |
| 1143 | pg_log_generic(PG_LOG_INFO, "Consider using a full dump instead of a --data-only dump to avoid this problem." ); |
| 1144 | if (nLoop > 1) |
| 1145 | removeObjectDependency(loop[0], loop[1]->dumpId); |
| 1146 | else /* must be a self-dependency */ |
| 1147 | removeObjectDependency(loop[0], loop[0]->dumpId); |
| 1148 | return; |
| 1149 | } |
| 1150 | |
| 1151 | /* |
| 1152 | * If we can't find a principled way to break the loop, complain and break |
| 1153 | * it in an arbitrary fashion. |
| 1154 | */ |
| 1155 | pg_log_warning("could not resolve dependency loop among these items:" ); |
| 1156 | for (i = 0; i < nLoop; i++) |
| 1157 | { |
| 1158 | char buf[1024]; |
| 1159 | |
| 1160 | describeDumpableObject(loop[i], buf, sizeof(buf)); |
| 1161 | pg_log_generic(PG_LOG_INFO, " %s" , buf); |
| 1162 | } |
| 1163 | |
| 1164 | if (nLoop > 1) |
| 1165 | removeObjectDependency(loop[0], loop[1]->dumpId); |
| 1166 | else /* must be a self-dependency */ |
| 1167 | removeObjectDependency(loop[0], loop[0]->dumpId); |
| 1168 | } |
| 1169 | |
| 1170 | /* |
| 1171 | * Describe a dumpable object usefully for errors |
| 1172 | * |
| 1173 | * This should probably go somewhere else... |
| 1174 | */ |
| 1175 | static void |
| 1176 | describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) |
| 1177 | { |
| 1178 | switch (obj->objType) |
| 1179 | { |
| 1180 | case DO_NAMESPACE: |
| 1181 | snprintf(buf, bufsize, |
| 1182 | "SCHEMA %s (ID %d OID %u)" , |
| 1183 | obj->name, obj->dumpId, obj->catId.oid); |
| 1184 | return; |
| 1185 | case DO_EXTENSION: |
| 1186 | snprintf(buf, bufsize, |
| 1187 | "EXTENSION %s (ID %d OID %u)" , |
| 1188 | obj->name, obj->dumpId, obj->catId.oid); |
| 1189 | return; |
| 1190 | case DO_TYPE: |
| 1191 | snprintf(buf, bufsize, |
| 1192 | "TYPE %s (ID %d OID %u)" , |
| 1193 | obj->name, obj->dumpId, obj->catId.oid); |
| 1194 | return; |
| 1195 | case DO_SHELL_TYPE: |
| 1196 | snprintf(buf, bufsize, |
| 1197 | "SHELL TYPE %s (ID %d OID %u)" , |
| 1198 | obj->name, obj->dumpId, obj->catId.oid); |
| 1199 | return; |
| 1200 | case DO_FUNC: |
| 1201 | snprintf(buf, bufsize, |
| 1202 | "FUNCTION %s (ID %d OID %u)" , |
| 1203 | obj->name, obj->dumpId, obj->catId.oid); |
| 1204 | return; |
| 1205 | case DO_AGG: |
| 1206 | snprintf(buf, bufsize, |
| 1207 | "AGGREGATE %s (ID %d OID %u)" , |
| 1208 | obj->name, obj->dumpId, obj->catId.oid); |
| 1209 | return; |
| 1210 | case DO_OPERATOR: |
| 1211 | snprintf(buf, bufsize, |
| 1212 | "OPERATOR %s (ID %d OID %u)" , |
| 1213 | obj->name, obj->dumpId, obj->catId.oid); |
| 1214 | return; |
| 1215 | case DO_ACCESS_METHOD: |
| 1216 | snprintf(buf, bufsize, |
| 1217 | "ACCESS METHOD %s (ID %d OID %u)" , |
| 1218 | obj->name, obj->dumpId, obj->catId.oid); |
| 1219 | return; |
| 1220 | case DO_OPCLASS: |
| 1221 | snprintf(buf, bufsize, |
| 1222 | "OPERATOR CLASS %s (ID %d OID %u)" , |
| 1223 | obj->name, obj->dumpId, obj->catId.oid); |
| 1224 | return; |
| 1225 | case DO_OPFAMILY: |
| 1226 | snprintf(buf, bufsize, |
| 1227 | "OPERATOR FAMILY %s (ID %d OID %u)" , |
| 1228 | obj->name, obj->dumpId, obj->catId.oid); |
| 1229 | return; |
| 1230 | case DO_COLLATION: |
| 1231 | snprintf(buf, bufsize, |
| 1232 | "COLLATION %s (ID %d OID %u)" , |
| 1233 | obj->name, obj->dumpId, obj->catId.oid); |
| 1234 | return; |
| 1235 | case DO_CONVERSION: |
| 1236 | snprintf(buf, bufsize, |
| 1237 | "CONVERSION %s (ID %d OID %u)" , |
| 1238 | obj->name, obj->dumpId, obj->catId.oid); |
| 1239 | return; |
| 1240 | case DO_TABLE: |
| 1241 | snprintf(buf, bufsize, |
| 1242 | "TABLE %s (ID %d OID %u)" , |
| 1243 | obj->name, obj->dumpId, obj->catId.oid); |
| 1244 | return; |
| 1245 | case DO_ATTRDEF: |
| 1246 | snprintf(buf, bufsize, |
| 1247 | "ATTRDEF %s.%s (ID %d OID %u)" , |
| 1248 | ((AttrDefInfo *) obj)->adtable->dobj.name, |
| 1249 | ((AttrDefInfo *) obj)->adtable->attnames[((AttrDefInfo *) obj)->adnum - 1], |
| 1250 | obj->dumpId, obj->catId.oid); |
| 1251 | return; |
| 1252 | case DO_INDEX: |
| 1253 | snprintf(buf, bufsize, |
| 1254 | "INDEX %s (ID %d OID %u)" , |
| 1255 | obj->name, obj->dumpId, obj->catId.oid); |
| 1256 | return; |
| 1257 | case DO_INDEX_ATTACH: |
| 1258 | snprintf(buf, bufsize, |
| 1259 | "INDEX ATTACH %s (ID %d)" , |
| 1260 | obj->name, obj->dumpId); |
| 1261 | return; |
| 1262 | case DO_STATSEXT: |
| 1263 | snprintf(buf, bufsize, |
| 1264 | "STATISTICS %s (ID %d OID %u)" , |
| 1265 | obj->name, obj->dumpId, obj->catId.oid); |
| 1266 | return; |
| 1267 | case DO_REFRESH_MATVIEW: |
| 1268 | snprintf(buf, bufsize, |
| 1269 | "REFRESH MATERIALIZED VIEW %s (ID %d OID %u)" , |
| 1270 | obj->name, obj->dumpId, obj->catId.oid); |
| 1271 | return; |
| 1272 | case DO_RULE: |
| 1273 | snprintf(buf, bufsize, |
| 1274 | "RULE %s (ID %d OID %u)" , |
| 1275 | obj->name, obj->dumpId, obj->catId.oid); |
| 1276 | return; |
| 1277 | case DO_TRIGGER: |
| 1278 | snprintf(buf, bufsize, |
| 1279 | "TRIGGER %s (ID %d OID %u)" , |
| 1280 | obj->name, obj->dumpId, obj->catId.oid); |
| 1281 | return; |
| 1282 | case DO_EVENT_TRIGGER: |
| 1283 | snprintf(buf, bufsize, |
| 1284 | "EVENT TRIGGER %s (ID %d OID %u)" , |
| 1285 | obj->name, obj->dumpId, obj->catId.oid); |
| 1286 | return; |
| 1287 | case DO_CONSTRAINT: |
| 1288 | snprintf(buf, bufsize, |
| 1289 | "CONSTRAINT %s (ID %d OID %u)" , |
| 1290 | obj->name, obj->dumpId, obj->catId.oid); |
| 1291 | return; |
| 1292 | case DO_FK_CONSTRAINT: |
| 1293 | snprintf(buf, bufsize, |
| 1294 | "FK CONSTRAINT %s (ID %d OID %u)" , |
| 1295 | obj->name, obj->dumpId, obj->catId.oid); |
| 1296 | return; |
| 1297 | case DO_PROCLANG: |
| 1298 | snprintf(buf, bufsize, |
| 1299 | "PROCEDURAL LANGUAGE %s (ID %d OID %u)" , |
| 1300 | obj->name, obj->dumpId, obj->catId.oid); |
| 1301 | return; |
| 1302 | case DO_CAST: |
| 1303 | snprintf(buf, bufsize, |
| 1304 | "CAST %u to %u (ID %d OID %u)" , |
| 1305 | ((CastInfo *) obj)->castsource, |
| 1306 | ((CastInfo *) obj)->casttarget, |
| 1307 | obj->dumpId, obj->catId.oid); |
| 1308 | return; |
| 1309 | case DO_TRANSFORM: |
| 1310 | snprintf(buf, bufsize, |
| 1311 | "TRANSFORM %u lang %u (ID %d OID %u)" , |
| 1312 | ((TransformInfo *) obj)->trftype, |
| 1313 | ((TransformInfo *) obj)->trflang, |
| 1314 | obj->dumpId, obj->catId.oid); |
| 1315 | return; |
| 1316 | case DO_TABLE_DATA: |
| 1317 | snprintf(buf, bufsize, |
| 1318 | "TABLE DATA %s (ID %d OID %u)" , |
| 1319 | obj->name, obj->dumpId, obj->catId.oid); |
| 1320 | return; |
| 1321 | case DO_SEQUENCE_SET: |
| 1322 | snprintf(buf, bufsize, |
| 1323 | "SEQUENCE SET %s (ID %d OID %u)" , |
| 1324 | obj->name, obj->dumpId, obj->catId.oid); |
| 1325 | return; |
| 1326 | case DO_DUMMY_TYPE: |
| 1327 | snprintf(buf, bufsize, |
| 1328 | "DUMMY TYPE %s (ID %d OID %u)" , |
| 1329 | obj->name, obj->dumpId, obj->catId.oid); |
| 1330 | return; |
| 1331 | case DO_TSPARSER: |
| 1332 | snprintf(buf, bufsize, |
| 1333 | "TEXT SEARCH PARSER %s (ID %d OID %u)" , |
| 1334 | obj->name, obj->dumpId, obj->catId.oid); |
| 1335 | return; |
| 1336 | case DO_TSDICT: |
| 1337 | snprintf(buf, bufsize, |
| 1338 | "TEXT SEARCH DICTIONARY %s (ID %d OID %u)" , |
| 1339 | obj->name, obj->dumpId, obj->catId.oid); |
| 1340 | return; |
| 1341 | case DO_TSTEMPLATE: |
| 1342 | snprintf(buf, bufsize, |
| 1343 | "TEXT SEARCH TEMPLATE %s (ID %d OID %u)" , |
| 1344 | obj->name, obj->dumpId, obj->catId.oid); |
| 1345 | return; |
| 1346 | case DO_TSCONFIG: |
| 1347 | snprintf(buf, bufsize, |
| 1348 | "TEXT SEARCH CONFIGURATION %s (ID %d OID %u)" , |
| 1349 | obj->name, obj->dumpId, obj->catId.oid); |
| 1350 | return; |
| 1351 | case DO_FDW: |
| 1352 | snprintf(buf, bufsize, |
| 1353 | "FOREIGN DATA WRAPPER %s (ID %d OID %u)" , |
| 1354 | obj->name, obj->dumpId, obj->catId.oid); |
| 1355 | return; |
| 1356 | case DO_FOREIGN_SERVER: |
| 1357 | snprintf(buf, bufsize, |
| 1358 | "FOREIGN SERVER %s (ID %d OID %u)" , |
| 1359 | obj->name, obj->dumpId, obj->catId.oid); |
| 1360 | return; |
| 1361 | case DO_DEFAULT_ACL: |
| 1362 | snprintf(buf, bufsize, |
| 1363 | "DEFAULT ACL %s (ID %d OID %u)" , |
| 1364 | obj->name, obj->dumpId, obj->catId.oid); |
| 1365 | return; |
| 1366 | case DO_BLOB: |
| 1367 | snprintf(buf, bufsize, |
| 1368 | "BLOB (ID %d OID %u)" , |
| 1369 | obj->dumpId, obj->catId.oid); |
| 1370 | return; |
| 1371 | case DO_BLOB_DATA: |
| 1372 | snprintf(buf, bufsize, |
| 1373 | "BLOB DATA (ID %d)" , |
| 1374 | obj->dumpId); |
| 1375 | return; |
| 1376 | case DO_POLICY: |
| 1377 | snprintf(buf, bufsize, |
| 1378 | "POLICY (ID %d OID %u)" , |
| 1379 | obj->dumpId, obj->catId.oid); |
| 1380 | return; |
| 1381 | case DO_PUBLICATION: |
| 1382 | snprintf(buf, bufsize, |
| 1383 | "PUBLICATION (ID %d OID %u)" , |
| 1384 | obj->dumpId, obj->catId.oid); |
| 1385 | return; |
| 1386 | case DO_PUBLICATION_REL: |
| 1387 | snprintf(buf, bufsize, |
| 1388 | "PUBLICATION TABLE (ID %d OID %u)" , |
| 1389 | obj->dumpId, obj->catId.oid); |
| 1390 | return; |
| 1391 | case DO_SUBSCRIPTION: |
| 1392 | snprintf(buf, bufsize, |
| 1393 | "SUBSCRIPTION (ID %d OID %u)" , |
| 1394 | obj->dumpId, obj->catId.oid); |
| 1395 | return; |
| 1396 | case DO_PRE_DATA_BOUNDARY: |
| 1397 | snprintf(buf, bufsize, |
| 1398 | "PRE-DATA BOUNDARY (ID %d)" , |
| 1399 | obj->dumpId); |
| 1400 | return; |
| 1401 | case DO_POST_DATA_BOUNDARY: |
| 1402 | snprintf(buf, bufsize, |
| 1403 | "POST-DATA BOUNDARY (ID %d)" , |
| 1404 | obj->dumpId); |
| 1405 | return; |
| 1406 | } |
| 1407 | /* shouldn't get here */ |
| 1408 | snprintf(buf, bufsize, |
| 1409 | "object type %d (ID %d OID %u)" , |
| 1410 | (int) obj->objType, |
| 1411 | obj->dumpId, obj->catId.oid); |
| 1412 | } |
| 1413 | |