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 | |