1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * common.c |
4 | * Catalog routines used by pg_dump; long ago these were shared |
5 | * by another dump tool, but not anymore. |
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/common.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 <ctype.h> |
23 | |
24 | #include "catalog/pg_class_d.h" |
25 | #include "fe_utils/string_utils.h" |
26 | |
27 | |
28 | /* |
29 | * Variables for mapping DumpId to DumpableObject |
30 | */ |
31 | static DumpableObject **dumpIdMap = NULL; |
32 | static int allocedDumpIds = 0; |
33 | static DumpId lastDumpId = 0; |
34 | |
35 | /* |
36 | * Variables for mapping CatalogId to DumpableObject |
37 | */ |
38 | static bool catalogIdMapValid = false; |
39 | static DumpableObject **catalogIdMap = NULL; |
40 | static int numCatalogIds = 0; |
41 | |
42 | /* |
43 | * These variables are static to avoid the notational cruft of having to pass |
44 | * them into findTableByOid() and friends. For each of these arrays, we build |
45 | * a sorted-by-OID index array immediately after the objects are fetched, |
46 | * and then we use binary search in findTableByOid() and friends. (qsort'ing |
47 | * the object arrays themselves would be simpler, but it doesn't work because |
48 | * pg_dump.c may have already established pointers between items.) |
49 | */ |
50 | static DumpableObject **tblinfoindex; |
51 | static DumpableObject **typinfoindex; |
52 | static DumpableObject **funinfoindex; |
53 | static DumpableObject **oprinfoindex; |
54 | static DumpableObject **collinfoindex; |
55 | static DumpableObject **nspinfoindex; |
56 | static DumpableObject **extinfoindex; |
57 | static int numTables; |
58 | static int numTypes; |
59 | static int numFuncs; |
60 | static int numOperators; |
61 | static int numCollations; |
62 | static int numNamespaces; |
63 | static int numExtensions; |
64 | |
65 | /* This is an array of object identities, not actual DumpableObjects */ |
66 | static ExtensionMemberId *extmembers; |
67 | static int numextmembers; |
68 | |
69 | static void flagInhTables(Archive *fout, TableInfo *tbinfo, int numTables, |
70 | InhInfo *inhinfo, int numInherits); |
71 | static void flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables); |
72 | static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables); |
73 | static DumpableObject **buildIndexArray(void *objArray, int numObjs, |
74 | Size objSize); |
75 | static int DOCatalogIdCompare(const void *p1, const void *p2); |
76 | static int ExtensionMemberIdCompare(const void *p1, const void *p2); |
77 | static void findParentsByOid(TableInfo *self, |
78 | InhInfo *inhinfo, int numInherits); |
79 | static int strInArray(const char *pattern, char **arr, int arr_size); |
80 | static IndxInfo *findIndexByOid(Oid oid, DumpableObject **idxinfoindex, |
81 | int numIndexes); |
82 | |
83 | |
84 | /* |
85 | * getSchemaData |
86 | * Collect information about all potentially dumpable objects |
87 | */ |
88 | TableInfo * |
89 | getSchemaData(Archive *fout, int *numTablesPtr) |
90 | { |
91 | TableInfo *tblinfo; |
92 | TypeInfo *typinfo; |
93 | FuncInfo *funinfo; |
94 | OprInfo *oprinfo; |
95 | CollInfo *collinfo; |
96 | NamespaceInfo *nspinfo; |
97 | ExtensionInfo *extinfo; |
98 | InhInfo *inhinfo; |
99 | int numAggregates; |
100 | int numInherits; |
101 | int numRules; |
102 | int numProcLangs; |
103 | int numCasts; |
104 | int numTransforms; |
105 | int numAccessMethods; |
106 | int numOpclasses; |
107 | int numOpfamilies; |
108 | int numConversions; |
109 | int numTSParsers; |
110 | int numTSTemplates; |
111 | int numTSDicts; |
112 | int numTSConfigs; |
113 | int numForeignDataWrappers; |
114 | int numForeignServers; |
115 | int numDefaultACLs; |
116 | int numEventTriggers; |
117 | |
118 | /* |
119 | * We must read extensions and extension membership info first, because |
120 | * extension membership needs to be consultable during decisions about |
121 | * whether other objects are to be dumped. |
122 | */ |
123 | pg_log_info("reading extensions" ); |
124 | extinfo = getExtensions(fout, &numExtensions); |
125 | extinfoindex = buildIndexArray(extinfo, numExtensions, sizeof(ExtensionInfo)); |
126 | |
127 | pg_log_info("identifying extension members" ); |
128 | getExtensionMembership(fout, extinfo, numExtensions); |
129 | |
130 | pg_log_info("reading schemas" ); |
131 | nspinfo = getNamespaces(fout, &numNamespaces); |
132 | nspinfoindex = buildIndexArray(nspinfo, numNamespaces, sizeof(NamespaceInfo)); |
133 | |
134 | /* |
135 | * getTables should be done as soon as possible, so as to minimize the |
136 | * window between starting our transaction and acquiring per-table locks. |
137 | * However, we have to do getNamespaces first because the tables get |
138 | * linked to their containing namespaces during getTables. |
139 | */ |
140 | pg_log_info("reading user-defined tables" ); |
141 | tblinfo = getTables(fout, &numTables); |
142 | tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo)); |
143 | |
144 | /* Do this after we've built tblinfoindex */ |
145 | getOwnedSeqs(fout, tblinfo, numTables); |
146 | |
147 | pg_log_info("reading user-defined functions" ); |
148 | funinfo = getFuncs(fout, &numFuncs); |
149 | funinfoindex = buildIndexArray(funinfo, numFuncs, sizeof(FuncInfo)); |
150 | |
151 | /* this must be after getTables and getFuncs */ |
152 | pg_log_info("reading user-defined types" ); |
153 | typinfo = getTypes(fout, &numTypes); |
154 | typinfoindex = buildIndexArray(typinfo, numTypes, sizeof(TypeInfo)); |
155 | |
156 | /* this must be after getFuncs, too */ |
157 | pg_log_info("reading procedural languages" ); |
158 | getProcLangs(fout, &numProcLangs); |
159 | |
160 | pg_log_info("reading user-defined aggregate functions" ); |
161 | getAggregates(fout, &numAggregates); |
162 | |
163 | pg_log_info("reading user-defined operators" ); |
164 | oprinfo = getOperators(fout, &numOperators); |
165 | oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo)); |
166 | |
167 | pg_log_info("reading user-defined access methods" ); |
168 | getAccessMethods(fout, &numAccessMethods); |
169 | |
170 | pg_log_info("reading user-defined operator classes" ); |
171 | getOpclasses(fout, &numOpclasses); |
172 | |
173 | pg_log_info("reading user-defined operator families" ); |
174 | getOpfamilies(fout, &numOpfamilies); |
175 | |
176 | pg_log_info("reading user-defined text search parsers" ); |
177 | getTSParsers(fout, &numTSParsers); |
178 | |
179 | pg_log_info("reading user-defined text search templates" ); |
180 | getTSTemplates(fout, &numTSTemplates); |
181 | |
182 | pg_log_info("reading user-defined text search dictionaries" ); |
183 | getTSDictionaries(fout, &numTSDicts); |
184 | |
185 | pg_log_info("reading user-defined text search configurations" ); |
186 | getTSConfigurations(fout, &numTSConfigs); |
187 | |
188 | pg_log_info("reading user-defined foreign-data wrappers" ); |
189 | getForeignDataWrappers(fout, &numForeignDataWrappers); |
190 | |
191 | pg_log_info("reading user-defined foreign servers" ); |
192 | getForeignServers(fout, &numForeignServers); |
193 | |
194 | pg_log_info("reading default privileges" ); |
195 | getDefaultACLs(fout, &numDefaultACLs); |
196 | |
197 | pg_log_info("reading user-defined collations" ); |
198 | collinfo = getCollations(fout, &numCollations); |
199 | collinfoindex = buildIndexArray(collinfo, numCollations, sizeof(CollInfo)); |
200 | |
201 | pg_log_info("reading user-defined conversions" ); |
202 | getConversions(fout, &numConversions); |
203 | |
204 | pg_log_info("reading type casts" ); |
205 | getCasts(fout, &numCasts); |
206 | |
207 | pg_log_info("reading transforms" ); |
208 | getTransforms(fout, &numTransforms); |
209 | |
210 | pg_log_info("reading table inheritance information" ); |
211 | inhinfo = getInherits(fout, &numInherits); |
212 | |
213 | pg_log_info("reading event triggers" ); |
214 | getEventTriggers(fout, &numEventTriggers); |
215 | |
216 | /* Identify extension configuration tables that should be dumped */ |
217 | pg_log_info("finding extension tables" ); |
218 | processExtensionTables(fout, extinfo, numExtensions); |
219 | |
220 | /* Link tables to parents, mark parents of target tables interesting */ |
221 | pg_log_info("finding inheritance relationships" ); |
222 | flagInhTables(fout, tblinfo, numTables, inhinfo, numInherits); |
223 | |
224 | pg_log_info("reading column info for interesting tables" ); |
225 | getTableAttrs(fout, tblinfo, numTables); |
226 | |
227 | pg_log_info("flagging inherited columns in subtables" ); |
228 | flagInhAttrs(fout->dopt, tblinfo, numTables); |
229 | |
230 | pg_log_info("reading indexes" ); |
231 | getIndexes(fout, tblinfo, numTables); |
232 | |
233 | pg_log_info("flagging indexes in partitioned tables" ); |
234 | flagInhIndexes(fout, tblinfo, numTables); |
235 | |
236 | pg_log_info("reading extended statistics" ); |
237 | getExtendedStatistics(fout); |
238 | |
239 | pg_log_info("reading constraints" ); |
240 | getConstraints(fout, tblinfo, numTables); |
241 | |
242 | pg_log_info("reading triggers" ); |
243 | getTriggers(fout, tblinfo, numTables); |
244 | |
245 | pg_log_info("reading rewrite rules" ); |
246 | getRules(fout, &numRules); |
247 | |
248 | pg_log_info("reading policies" ); |
249 | getPolicies(fout, tblinfo, numTables); |
250 | |
251 | pg_log_info("reading publications" ); |
252 | getPublications(fout); |
253 | |
254 | pg_log_info("reading publication membership" ); |
255 | getPublicationTables(fout, tblinfo, numTables); |
256 | |
257 | pg_log_info("reading subscriptions" ); |
258 | getSubscriptions(fout); |
259 | |
260 | *numTablesPtr = numTables; |
261 | return tblinfo; |
262 | } |
263 | |
264 | /* flagInhTables - |
265 | * Fill in parent link fields of tables for which we need that information, |
266 | * and mark parents of target tables as interesting |
267 | * |
268 | * Note that only direct ancestors of targets are marked interesting. |
269 | * This is sufficient; we don't much care whether they inherited their |
270 | * attributes or not. |
271 | * |
272 | * modifies tblinfo |
273 | */ |
274 | static void |
275 | flagInhTables(Archive *fout, TableInfo *tblinfo, int numTables, |
276 | InhInfo *inhinfo, int numInherits) |
277 | { |
278 | DumpOptions *dopt = fout->dopt; |
279 | int i, |
280 | j; |
281 | |
282 | for (i = 0; i < numTables; i++) |
283 | { |
284 | bool find_parents = true; |
285 | bool mark_parents = true; |
286 | |
287 | /* Some kinds never have parents */ |
288 | if (tblinfo[i].relkind == RELKIND_SEQUENCE || |
289 | tblinfo[i].relkind == RELKIND_VIEW || |
290 | tblinfo[i].relkind == RELKIND_MATVIEW) |
291 | continue; |
292 | |
293 | /* |
294 | * Normally, we don't bother computing anything for non-target tables, |
295 | * but if load-via-partition-root is specified, we gather information |
296 | * on every partition in the system so that getRootTableInfo can trace |
297 | * from any given to leaf partition all the way up to the root. (We |
298 | * don't need to mark them as interesting for getTableAttrs, though.) |
299 | */ |
300 | if (!tblinfo[i].dobj.dump) |
301 | { |
302 | mark_parents = false; |
303 | |
304 | if (!dopt->load_via_partition_root || |
305 | !tblinfo[i].ispartition) |
306 | find_parents = false; |
307 | } |
308 | |
309 | /* If needed, find all the immediate parent tables. */ |
310 | if (find_parents) |
311 | findParentsByOid(&tblinfo[i], inhinfo, numInherits); |
312 | |
313 | /* |
314 | * If needed, mark the parents as interesting for getTableAttrs and |
315 | * getIndexes. |
316 | */ |
317 | if (mark_parents) |
318 | { |
319 | int numParents = tblinfo[i].numParents; |
320 | TableInfo **parents = tblinfo[i].parents; |
321 | |
322 | for (j = 0; j < numParents; j++) |
323 | parents[j]->interesting = true; |
324 | } |
325 | } |
326 | } |
327 | |
328 | /* |
329 | * flagInhIndexes - |
330 | * Create IndexAttachInfo objects for partitioned indexes, and add |
331 | * appropriate dependency links. |
332 | */ |
333 | static void |
334 | flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables) |
335 | { |
336 | int i, |
337 | j, |
338 | k; |
339 | DumpableObject ***parentIndexArray; |
340 | |
341 | parentIndexArray = (DumpableObject ***) |
342 | pg_malloc0(getMaxDumpId() * sizeof(DumpableObject **)); |
343 | |
344 | for (i = 0; i < numTables; i++) |
345 | { |
346 | TableInfo *parenttbl; |
347 | IndexAttachInfo *attachinfo; |
348 | |
349 | if (!tblinfo[i].ispartition || tblinfo[i].numParents == 0) |
350 | continue; |
351 | |
352 | Assert(tblinfo[i].numParents == 1); |
353 | parenttbl = tblinfo[i].parents[0]; |
354 | |
355 | /* |
356 | * We need access to each parent table's index list, but there is no |
357 | * index to cover them outside of this function. To avoid having to |
358 | * sort every parent table's indexes each time we come across each of |
359 | * its partitions, create an indexed array for each parent the first |
360 | * time it is required. |
361 | */ |
362 | if (parentIndexArray[parenttbl->dobj.dumpId] == NULL) |
363 | parentIndexArray[parenttbl->dobj.dumpId] = |
364 | buildIndexArray(parenttbl->indexes, |
365 | parenttbl->numIndexes, |
366 | sizeof(IndxInfo)); |
367 | |
368 | attachinfo = (IndexAttachInfo *) |
369 | pg_malloc0(tblinfo[i].numIndexes * sizeof(IndexAttachInfo)); |
370 | for (j = 0, k = 0; j < tblinfo[i].numIndexes; j++) |
371 | { |
372 | IndxInfo *index = &(tblinfo[i].indexes[j]); |
373 | IndxInfo *parentidx; |
374 | |
375 | if (index->parentidx == 0) |
376 | continue; |
377 | |
378 | parentidx = findIndexByOid(index->parentidx, |
379 | parentIndexArray[parenttbl->dobj.dumpId], |
380 | parenttbl->numIndexes); |
381 | if (parentidx == NULL) |
382 | continue; |
383 | |
384 | attachinfo[k].dobj.objType = DO_INDEX_ATTACH; |
385 | attachinfo[k].dobj.catId.tableoid = 0; |
386 | attachinfo[k].dobj.catId.oid = 0; |
387 | AssignDumpId(&attachinfo[k].dobj); |
388 | attachinfo[k].dobj.name = pg_strdup(index->dobj.name); |
389 | attachinfo[k].dobj.namespace = index->indextable->dobj.namespace; |
390 | attachinfo[k].parentIdx = parentidx; |
391 | attachinfo[k].partitionIdx = index; |
392 | |
393 | /* |
394 | * We must state the DO_INDEX_ATTACH object's dependencies |
395 | * explicitly, since it will not match anything in pg_depend. |
396 | * |
397 | * Give it dependencies on both the partition index and the parent |
398 | * index, so that it will not be executed till both of those |
399 | * exist. (There's no need to care what order those are created |
400 | * in.) |
401 | * |
402 | * In addition, give it dependencies on the indexes' underlying |
403 | * tables. This does nothing of great value so far as serial |
404 | * restore ordering goes, but it ensures that a parallel restore |
405 | * will not try to run the ATTACH concurrently with other |
406 | * operations on those tables. |
407 | */ |
408 | addObjectDependency(&attachinfo[k].dobj, index->dobj.dumpId); |
409 | addObjectDependency(&attachinfo[k].dobj, parentidx->dobj.dumpId); |
410 | addObjectDependency(&attachinfo[k].dobj, |
411 | index->indextable->dobj.dumpId); |
412 | addObjectDependency(&attachinfo[k].dobj, |
413 | parentidx->indextable->dobj.dumpId); |
414 | |
415 | k++; |
416 | } |
417 | } |
418 | |
419 | for (i = 0; i < numTables; i++) |
420 | if (parentIndexArray[i]) |
421 | pg_free(parentIndexArray[i]); |
422 | pg_free(parentIndexArray); |
423 | } |
424 | |
425 | /* flagInhAttrs - |
426 | * for each dumpable table in tblinfo, flag its inherited attributes |
427 | * |
428 | * What we need to do here is detect child columns that inherit NOT NULL |
429 | * bits from their parents (so that we needn't specify that again for the |
430 | * child) and child columns that have DEFAULT NULL when their parents had |
431 | * some non-null default. In the latter case, we make up a dummy AttrDefInfo |
432 | * object so that we'll correctly emit the necessary DEFAULT NULL clause; |
433 | * otherwise the backend will apply an inherited default to the column. |
434 | * |
435 | * modifies tblinfo |
436 | */ |
437 | static void |
438 | flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables) |
439 | { |
440 | int i, |
441 | j, |
442 | k; |
443 | |
444 | for (i = 0; i < numTables; i++) |
445 | { |
446 | TableInfo *tbinfo = &(tblinfo[i]); |
447 | int numParents; |
448 | TableInfo **parents; |
449 | |
450 | /* Some kinds never have parents */ |
451 | if (tbinfo->relkind == RELKIND_SEQUENCE || |
452 | tbinfo->relkind == RELKIND_VIEW || |
453 | tbinfo->relkind == RELKIND_MATVIEW) |
454 | continue; |
455 | |
456 | /* Don't bother computing anything for non-target tables, either */ |
457 | if (!tbinfo->dobj.dump) |
458 | continue; |
459 | |
460 | numParents = tbinfo->numParents; |
461 | parents = tbinfo->parents; |
462 | |
463 | if (numParents == 0) |
464 | continue; /* nothing to see here, move along */ |
465 | |
466 | /* For each column, search for matching column names in parent(s) */ |
467 | for (j = 0; j < tbinfo->numatts; j++) |
468 | { |
469 | bool foundNotNull; /* Attr was NOT NULL in a parent */ |
470 | bool foundDefault; /* Found a default in a parent */ |
471 | |
472 | /* no point in examining dropped columns */ |
473 | if (tbinfo->attisdropped[j]) |
474 | continue; |
475 | |
476 | foundNotNull = false; |
477 | foundDefault = false; |
478 | for (k = 0; k < numParents; k++) |
479 | { |
480 | TableInfo *parent = parents[k]; |
481 | int inhAttrInd; |
482 | |
483 | inhAttrInd = strInArray(tbinfo->attnames[j], |
484 | parent->attnames, |
485 | parent->numatts); |
486 | if (inhAttrInd >= 0) |
487 | { |
488 | foundNotNull |= parent->notnull[inhAttrInd]; |
489 | foundDefault |= (parent->attrdefs[inhAttrInd] != NULL); |
490 | } |
491 | } |
492 | |
493 | /* Remember if we found inherited NOT NULL */ |
494 | tbinfo->inhNotNull[j] = foundNotNull; |
495 | |
496 | /* Manufacture a DEFAULT NULL clause if necessary */ |
497 | if (foundDefault && tbinfo->attrdefs[j] == NULL) |
498 | { |
499 | AttrDefInfo *attrDef; |
500 | |
501 | attrDef = (AttrDefInfo *) pg_malloc(sizeof(AttrDefInfo)); |
502 | attrDef->dobj.objType = DO_ATTRDEF; |
503 | attrDef->dobj.catId.tableoid = 0; |
504 | attrDef->dobj.catId.oid = 0; |
505 | AssignDumpId(&attrDef->dobj); |
506 | attrDef->dobj.name = pg_strdup(tbinfo->dobj.name); |
507 | attrDef->dobj.namespace = tbinfo->dobj.namespace; |
508 | attrDef->dobj.dump = tbinfo->dobj.dump; |
509 | |
510 | attrDef->adtable = tbinfo; |
511 | attrDef->adnum = j + 1; |
512 | attrDef->adef_expr = pg_strdup("NULL" ); |
513 | |
514 | /* Will column be dumped explicitly? */ |
515 | if (shouldPrintColumn(dopt, tbinfo, j)) |
516 | { |
517 | attrDef->separate = false; |
518 | /* No dependency needed: NULL cannot have dependencies */ |
519 | } |
520 | else |
521 | { |
522 | /* column will be suppressed, print default separately */ |
523 | attrDef->separate = true; |
524 | /* ensure it comes out after the table */ |
525 | addObjectDependency(&attrDef->dobj, |
526 | tbinfo->dobj.dumpId); |
527 | } |
528 | |
529 | tbinfo->attrdefs[j] = attrDef; |
530 | } |
531 | } |
532 | } |
533 | } |
534 | |
535 | /* |
536 | * AssignDumpId |
537 | * Given a newly-created dumpable object, assign a dump ID, |
538 | * and enter the object into the lookup table. |
539 | * |
540 | * The caller is expected to have filled in objType and catId, |
541 | * but not any of the other standard fields of a DumpableObject. |
542 | */ |
543 | void |
544 | AssignDumpId(DumpableObject *dobj) |
545 | { |
546 | dobj->dumpId = ++lastDumpId; |
547 | dobj->name = NULL; /* must be set later */ |
548 | dobj->namespace = NULL; /* may be set later */ |
549 | dobj->dump = DUMP_COMPONENT_ALL; /* default assumption */ |
550 | dobj->ext_member = false; /* default assumption */ |
551 | dobj->dependencies = NULL; |
552 | dobj->nDeps = 0; |
553 | dobj->allocDeps = 0; |
554 | |
555 | while (dobj->dumpId >= allocedDumpIds) |
556 | { |
557 | int newAlloc; |
558 | |
559 | if (allocedDumpIds <= 0) |
560 | { |
561 | newAlloc = 256; |
562 | dumpIdMap = (DumpableObject **) |
563 | pg_malloc(newAlloc * sizeof(DumpableObject *)); |
564 | } |
565 | else |
566 | { |
567 | newAlloc = allocedDumpIds * 2; |
568 | dumpIdMap = (DumpableObject **) |
569 | pg_realloc(dumpIdMap, newAlloc * sizeof(DumpableObject *)); |
570 | } |
571 | memset(dumpIdMap + allocedDumpIds, 0, |
572 | (newAlloc - allocedDumpIds) * sizeof(DumpableObject *)); |
573 | allocedDumpIds = newAlloc; |
574 | } |
575 | dumpIdMap[dobj->dumpId] = dobj; |
576 | |
577 | /* mark catalogIdMap invalid, but don't rebuild it yet */ |
578 | catalogIdMapValid = false; |
579 | } |
580 | |
581 | /* |
582 | * Assign a DumpId that's not tied to a DumpableObject. |
583 | * |
584 | * This is used when creating a "fixed" ArchiveEntry that doesn't need to |
585 | * participate in the sorting logic. |
586 | */ |
587 | DumpId |
588 | createDumpId(void) |
589 | { |
590 | return ++lastDumpId; |
591 | } |
592 | |
593 | /* |
594 | * Return the largest DumpId so far assigned |
595 | */ |
596 | DumpId |
597 | getMaxDumpId(void) |
598 | { |
599 | return lastDumpId; |
600 | } |
601 | |
602 | /* |
603 | * Find a DumpableObject by dump ID |
604 | * |
605 | * Returns NULL for invalid ID |
606 | */ |
607 | DumpableObject * |
608 | findObjectByDumpId(DumpId dumpId) |
609 | { |
610 | if (dumpId <= 0 || dumpId >= allocedDumpIds) |
611 | return NULL; /* out of range? */ |
612 | return dumpIdMap[dumpId]; |
613 | } |
614 | |
615 | /* |
616 | * Find a DumpableObject by catalog ID |
617 | * |
618 | * Returns NULL for unknown ID |
619 | * |
620 | * We use binary search in a sorted list that is built on first call. |
621 | * If AssignDumpId() and findObjectByCatalogId() calls were freely intermixed, |
622 | * the code would work, but possibly be very slow. In the current usage |
623 | * pattern that does not happen, indeed we build the list at most twice. |
624 | */ |
625 | DumpableObject * |
626 | findObjectByCatalogId(CatalogId catalogId) |
627 | { |
628 | DumpableObject **low; |
629 | DumpableObject **high; |
630 | |
631 | if (!catalogIdMapValid) |
632 | { |
633 | if (catalogIdMap) |
634 | free(catalogIdMap); |
635 | getDumpableObjects(&catalogIdMap, &numCatalogIds); |
636 | if (numCatalogIds > 1) |
637 | qsort((void *) catalogIdMap, numCatalogIds, |
638 | sizeof(DumpableObject *), DOCatalogIdCompare); |
639 | catalogIdMapValid = true; |
640 | } |
641 | |
642 | /* |
643 | * We could use bsearch() here, but the notational cruft of calling |
644 | * bsearch is nearly as bad as doing it ourselves; and the generalized |
645 | * bsearch function is noticeably slower as well. |
646 | */ |
647 | if (numCatalogIds <= 0) |
648 | return NULL; |
649 | low = catalogIdMap; |
650 | high = catalogIdMap + (numCatalogIds - 1); |
651 | while (low <= high) |
652 | { |
653 | DumpableObject **middle; |
654 | int difference; |
655 | |
656 | middle = low + (high - low) / 2; |
657 | /* comparison must match DOCatalogIdCompare, below */ |
658 | difference = oidcmp((*middle)->catId.oid, catalogId.oid); |
659 | if (difference == 0) |
660 | difference = oidcmp((*middle)->catId.tableoid, catalogId.tableoid); |
661 | if (difference == 0) |
662 | return *middle; |
663 | else if (difference < 0) |
664 | low = middle + 1; |
665 | else |
666 | high = middle - 1; |
667 | } |
668 | return NULL; |
669 | } |
670 | |
671 | /* |
672 | * Find a DumpableObject by OID, in a pre-sorted array of one type of object |
673 | * |
674 | * Returns NULL for unknown OID |
675 | */ |
676 | static DumpableObject * |
677 | findObjectByOid(Oid oid, DumpableObject **indexArray, int numObjs) |
678 | { |
679 | DumpableObject **low; |
680 | DumpableObject **high; |
681 | |
682 | /* |
683 | * This is the same as findObjectByCatalogId except we assume we need not |
684 | * look at table OID because the objects are all the same type. |
685 | * |
686 | * We could use bsearch() here, but the notational cruft of calling |
687 | * bsearch is nearly as bad as doing it ourselves; and the generalized |
688 | * bsearch function is noticeably slower as well. |
689 | */ |
690 | if (numObjs <= 0) |
691 | return NULL; |
692 | low = indexArray; |
693 | high = indexArray + (numObjs - 1); |
694 | while (low <= high) |
695 | { |
696 | DumpableObject **middle; |
697 | int difference; |
698 | |
699 | middle = low + (high - low) / 2; |
700 | difference = oidcmp((*middle)->catId.oid, oid); |
701 | if (difference == 0) |
702 | return *middle; |
703 | else if (difference < 0) |
704 | low = middle + 1; |
705 | else |
706 | high = middle - 1; |
707 | } |
708 | return NULL; |
709 | } |
710 | |
711 | /* |
712 | * Build an index array of DumpableObject pointers, sorted by OID |
713 | */ |
714 | static DumpableObject ** |
715 | buildIndexArray(void *objArray, int numObjs, Size objSize) |
716 | { |
717 | DumpableObject **ptrs; |
718 | int i; |
719 | |
720 | ptrs = (DumpableObject **) pg_malloc(numObjs * sizeof(DumpableObject *)); |
721 | for (i = 0; i < numObjs; i++) |
722 | ptrs[i] = (DumpableObject *) ((char *) objArray + i * objSize); |
723 | |
724 | /* We can use DOCatalogIdCompare to sort since its first key is OID */ |
725 | if (numObjs > 1) |
726 | qsort((void *) ptrs, numObjs, sizeof(DumpableObject *), |
727 | DOCatalogIdCompare); |
728 | |
729 | return ptrs; |
730 | } |
731 | |
732 | /* |
733 | * qsort comparator for pointers to DumpableObjects |
734 | */ |
735 | static int |
736 | DOCatalogIdCompare(const void *p1, const void *p2) |
737 | { |
738 | const DumpableObject *obj1 = *(DumpableObject *const *) p1; |
739 | const DumpableObject *obj2 = *(DumpableObject *const *) p2; |
740 | int cmpval; |
741 | |
742 | /* |
743 | * Compare OID first since it's usually unique, whereas there will only be |
744 | * a few distinct values of tableoid. |
745 | */ |
746 | cmpval = oidcmp(obj1->catId.oid, obj2->catId.oid); |
747 | if (cmpval == 0) |
748 | cmpval = oidcmp(obj1->catId.tableoid, obj2->catId.tableoid); |
749 | return cmpval; |
750 | } |
751 | |
752 | /* |
753 | * Build an array of pointers to all known dumpable objects |
754 | * |
755 | * This simply creates a modifiable copy of the internal map. |
756 | */ |
757 | void |
758 | getDumpableObjects(DumpableObject ***objs, int *numObjs) |
759 | { |
760 | int i, |
761 | j; |
762 | |
763 | *objs = (DumpableObject **) |
764 | pg_malloc(allocedDumpIds * sizeof(DumpableObject *)); |
765 | j = 0; |
766 | for (i = 1; i < allocedDumpIds; i++) |
767 | { |
768 | if (dumpIdMap[i]) |
769 | (*objs)[j++] = dumpIdMap[i]; |
770 | } |
771 | *numObjs = j; |
772 | } |
773 | |
774 | /* |
775 | * Add a dependency link to a DumpableObject |
776 | * |
777 | * Note: duplicate dependencies are currently not eliminated |
778 | */ |
779 | void |
780 | addObjectDependency(DumpableObject *dobj, DumpId refId) |
781 | { |
782 | if (dobj->nDeps >= dobj->allocDeps) |
783 | { |
784 | if (dobj->allocDeps <= 0) |
785 | { |
786 | dobj->allocDeps = 16; |
787 | dobj->dependencies = (DumpId *) |
788 | pg_malloc(dobj->allocDeps * sizeof(DumpId)); |
789 | } |
790 | else |
791 | { |
792 | dobj->allocDeps *= 2; |
793 | dobj->dependencies = (DumpId *) |
794 | pg_realloc(dobj->dependencies, |
795 | dobj->allocDeps * sizeof(DumpId)); |
796 | } |
797 | } |
798 | dobj->dependencies[dobj->nDeps++] = refId; |
799 | } |
800 | |
801 | /* |
802 | * Remove a dependency link from a DumpableObject |
803 | * |
804 | * If there are multiple links, all are removed |
805 | */ |
806 | void |
807 | removeObjectDependency(DumpableObject *dobj, DumpId refId) |
808 | { |
809 | int i; |
810 | int j = 0; |
811 | |
812 | for (i = 0; i < dobj->nDeps; i++) |
813 | { |
814 | if (dobj->dependencies[i] != refId) |
815 | dobj->dependencies[j++] = dobj->dependencies[i]; |
816 | } |
817 | dobj->nDeps = j; |
818 | } |
819 | |
820 | |
821 | /* |
822 | * findTableByOid |
823 | * finds the entry (in tblinfo) of the table with the given oid |
824 | * returns NULL if not found |
825 | */ |
826 | TableInfo * |
827 | findTableByOid(Oid oid) |
828 | { |
829 | return (TableInfo *) findObjectByOid(oid, tblinfoindex, numTables); |
830 | } |
831 | |
832 | /* |
833 | * findTypeByOid |
834 | * finds the entry (in typinfo) of the type with the given oid |
835 | * returns NULL if not found |
836 | */ |
837 | TypeInfo * |
838 | findTypeByOid(Oid oid) |
839 | { |
840 | return (TypeInfo *) findObjectByOid(oid, typinfoindex, numTypes); |
841 | } |
842 | |
843 | /* |
844 | * findFuncByOid |
845 | * finds the entry (in funinfo) of the function with the given oid |
846 | * returns NULL if not found |
847 | */ |
848 | FuncInfo * |
849 | findFuncByOid(Oid oid) |
850 | { |
851 | return (FuncInfo *) findObjectByOid(oid, funinfoindex, numFuncs); |
852 | } |
853 | |
854 | /* |
855 | * findOprByOid |
856 | * finds the entry (in oprinfo) of the operator with the given oid |
857 | * returns NULL if not found |
858 | */ |
859 | OprInfo * |
860 | findOprByOid(Oid oid) |
861 | { |
862 | return (OprInfo *) findObjectByOid(oid, oprinfoindex, numOperators); |
863 | } |
864 | |
865 | /* |
866 | * findCollationByOid |
867 | * finds the entry (in collinfo) of the collation with the given oid |
868 | * returns NULL if not found |
869 | */ |
870 | CollInfo * |
871 | findCollationByOid(Oid oid) |
872 | { |
873 | return (CollInfo *) findObjectByOid(oid, collinfoindex, numCollations); |
874 | } |
875 | |
876 | /* |
877 | * findNamespaceByOid |
878 | * finds the entry (in nspinfo) of the namespace with the given oid |
879 | * returns NULL if not found |
880 | */ |
881 | NamespaceInfo * |
882 | findNamespaceByOid(Oid oid) |
883 | { |
884 | return (NamespaceInfo *) findObjectByOid(oid, nspinfoindex, numNamespaces); |
885 | } |
886 | |
887 | /* |
888 | * findExtensionByOid |
889 | * finds the entry (in extinfo) of the extension with the given oid |
890 | * returns NULL if not found |
891 | */ |
892 | ExtensionInfo * |
893 | findExtensionByOid(Oid oid) |
894 | { |
895 | return (ExtensionInfo *) findObjectByOid(oid, extinfoindex, numExtensions); |
896 | } |
897 | |
898 | /* |
899 | * findIndexByOid |
900 | * find the entry of the index with the given oid |
901 | * |
902 | * This one's signature is different from the previous ones because we lack a |
903 | * global array of all indexes, so caller must pass their array as argument. |
904 | */ |
905 | static IndxInfo * |
906 | findIndexByOid(Oid oid, DumpableObject **idxinfoindex, int numIndexes) |
907 | { |
908 | return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes); |
909 | } |
910 | |
911 | /* |
912 | * setExtensionMembership |
913 | * accept and save data about which objects belong to extensions |
914 | */ |
915 | void |
916 | setExtensionMembership(ExtensionMemberId *extmems, int nextmems) |
917 | { |
918 | /* Sort array in preparation for binary searches */ |
919 | if (nextmems > 1) |
920 | qsort((void *) extmems, nextmems, sizeof(ExtensionMemberId), |
921 | ExtensionMemberIdCompare); |
922 | /* And save */ |
923 | extmembers = extmems; |
924 | numextmembers = nextmems; |
925 | } |
926 | |
927 | /* |
928 | * findOwningExtension |
929 | * return owning extension for specified catalog ID, or NULL if none |
930 | */ |
931 | ExtensionInfo * |
932 | findOwningExtension(CatalogId catalogId) |
933 | { |
934 | ExtensionMemberId *low; |
935 | ExtensionMemberId *high; |
936 | |
937 | /* |
938 | * We could use bsearch() here, but the notational cruft of calling |
939 | * bsearch is nearly as bad as doing it ourselves; and the generalized |
940 | * bsearch function is noticeably slower as well. |
941 | */ |
942 | if (numextmembers <= 0) |
943 | return NULL; |
944 | low = extmembers; |
945 | high = extmembers + (numextmembers - 1); |
946 | while (low <= high) |
947 | { |
948 | ExtensionMemberId *middle; |
949 | int difference; |
950 | |
951 | middle = low + (high - low) / 2; |
952 | /* comparison must match ExtensionMemberIdCompare, below */ |
953 | difference = oidcmp(middle->catId.oid, catalogId.oid); |
954 | if (difference == 0) |
955 | difference = oidcmp(middle->catId.tableoid, catalogId.tableoid); |
956 | if (difference == 0) |
957 | return middle->ext; |
958 | else if (difference < 0) |
959 | low = middle + 1; |
960 | else |
961 | high = middle - 1; |
962 | } |
963 | return NULL; |
964 | } |
965 | |
966 | /* |
967 | * qsort comparator for ExtensionMemberIds |
968 | */ |
969 | static int |
970 | ExtensionMemberIdCompare(const void *p1, const void *p2) |
971 | { |
972 | const ExtensionMemberId *obj1 = (const ExtensionMemberId *) p1; |
973 | const ExtensionMemberId *obj2 = (const ExtensionMemberId *) p2; |
974 | int cmpval; |
975 | |
976 | /* |
977 | * Compare OID first since it's usually unique, whereas there will only be |
978 | * a few distinct values of tableoid. |
979 | */ |
980 | cmpval = oidcmp(obj1->catId.oid, obj2->catId.oid); |
981 | if (cmpval == 0) |
982 | cmpval = oidcmp(obj1->catId.tableoid, obj2->catId.tableoid); |
983 | return cmpval; |
984 | } |
985 | |
986 | |
987 | /* |
988 | * findParentsByOid |
989 | * find a table's parents in tblinfo[] |
990 | */ |
991 | static void |
992 | findParentsByOid(TableInfo *self, |
993 | InhInfo *inhinfo, int numInherits) |
994 | { |
995 | Oid oid = self->dobj.catId.oid; |
996 | int i, |
997 | j; |
998 | int numParents; |
999 | |
1000 | numParents = 0; |
1001 | for (i = 0; i < numInherits; i++) |
1002 | { |
1003 | if (inhinfo[i].inhrelid == oid) |
1004 | numParents++; |
1005 | } |
1006 | |
1007 | self->numParents = numParents; |
1008 | |
1009 | if (numParents > 0) |
1010 | { |
1011 | self->parents = (TableInfo **) |
1012 | pg_malloc(sizeof(TableInfo *) * numParents); |
1013 | j = 0; |
1014 | for (i = 0; i < numInherits; i++) |
1015 | { |
1016 | if (inhinfo[i].inhrelid == oid) |
1017 | { |
1018 | TableInfo *parent; |
1019 | |
1020 | parent = findTableByOid(inhinfo[i].inhparent); |
1021 | if (parent == NULL) |
1022 | { |
1023 | pg_log_error("failed sanity check, parent OID %u of table \"%s\" (OID %u) not found" , |
1024 | inhinfo[i].inhparent, |
1025 | self->dobj.name, |
1026 | oid); |
1027 | exit_nicely(1); |
1028 | } |
1029 | self->parents[j++] = parent; |
1030 | } |
1031 | } |
1032 | } |
1033 | else |
1034 | self->parents = NULL; |
1035 | } |
1036 | |
1037 | /* |
1038 | * parseOidArray |
1039 | * parse a string of numbers delimited by spaces into a character array |
1040 | * |
1041 | * Note: actually this is used for both Oids and potentially-signed |
1042 | * attribute numbers. This should cause no trouble, but we could split |
1043 | * the function into two functions with different argument types if it does. |
1044 | */ |
1045 | |
1046 | void |
1047 | parseOidArray(const char *str, Oid *array, int arraysize) |
1048 | { |
1049 | int j, |
1050 | argNum; |
1051 | char temp[100]; |
1052 | char s; |
1053 | |
1054 | argNum = 0; |
1055 | j = 0; |
1056 | for (;;) |
1057 | { |
1058 | s = *str++; |
1059 | if (s == ' ' || s == '\0') |
1060 | { |
1061 | if (j > 0) |
1062 | { |
1063 | if (argNum >= arraysize) |
1064 | { |
1065 | pg_log_error("could not parse numeric array \"%s\": too many numbers" , str); |
1066 | exit_nicely(1); |
1067 | } |
1068 | temp[j] = '\0'; |
1069 | array[argNum++] = atooid(temp); |
1070 | j = 0; |
1071 | } |
1072 | if (s == '\0') |
1073 | break; |
1074 | } |
1075 | else |
1076 | { |
1077 | if (!(isdigit((unsigned char) s) || s == '-') || |
1078 | j >= sizeof(temp) - 1) |
1079 | { |
1080 | pg_log_error("could not parse numeric array \"%s\": invalid character in number" , str); |
1081 | exit_nicely(1); |
1082 | } |
1083 | temp[j++] = s; |
1084 | } |
1085 | } |
1086 | |
1087 | while (argNum < arraysize) |
1088 | array[argNum++] = InvalidOid; |
1089 | } |
1090 | |
1091 | |
1092 | /* |
1093 | * strInArray: |
1094 | * takes in a string and a string array and the number of elements in the |
1095 | * string array. |
1096 | * returns the index if the string is somewhere in the array, -1 otherwise |
1097 | */ |
1098 | |
1099 | static int |
1100 | strInArray(const char *pattern, char **arr, int arr_size) |
1101 | { |
1102 | int i; |
1103 | |
1104 | for (i = 0; i < arr_size; i++) |
1105 | { |
1106 | if (strcmp(pattern, arr[i]) == 0) |
1107 | return i; |
1108 | } |
1109 | return -1; |
1110 | } |
1111 | |