Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * partdesc.c
4 : : * Support routines for manipulating partition descriptors
5 : : *
6 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/partitioning/partdesc.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include "access/genam.h"
18 : : #include "access/htup_details.h"
19 : : #include "access/table.h"
20 : : #include "catalog/partition.h"
21 : : #include "catalog/pg_inherits.h"
22 : : #include "partitioning/partbounds.h"
23 : : #include "partitioning/partdesc.h"
24 : : #include "utils/builtins.h"
25 : : #include "utils/fmgroids.h"
26 : : #include "utils/hsearch.h"
27 : : #include "utils/inval.h"
28 : : #include "utils/lsyscache.h"
29 : : #include "utils/memutils.h"
30 : : #include "utils/partcache.h"
31 : : #include "utils/rel.h"
32 : : #include "utils/snapmgr.h"
33 : : #include "utils/syscache.h"
34 : :
35 : : typedef struct PartitionDirectoryData
36 : : {
37 : : MemoryContext pdir_mcxt;
38 : : HTAB *pdir_hash;
39 : : bool omit_detached;
40 : : } PartitionDirectoryData;
41 : :
42 : : typedef struct PartitionDirectoryEntry
43 : : {
44 : : Oid reloid;
45 : : Relation rel;
46 : : PartitionDesc pd;
47 : : } PartitionDirectoryEntry;
48 : :
49 : : static PartitionDesc RelationBuildPartitionDesc(Relation rel,
50 : : bool omit_detached);
51 : :
52 : :
53 : : /*
54 : : * RelationGetPartitionDesc -- get partition descriptor, if relation is partitioned
55 : : *
56 : : * We keep two partdescs in relcache: rd_partdesc includes all partitions
57 : : * (even those being concurrently marked detached), while rd_partdesc_nodetached
58 : : * omits (some of) those. We store the pg_inherits.xmin value for the latter,
59 : : * to determine whether it can be validly reused in each case, since that
60 : : * depends on the active snapshot.
61 : : *
62 : : * Note: we arrange for partition descriptors to not get freed until the
63 : : * relcache entry's refcount goes to zero (see hacks in RelationClose,
64 : : * RelationClearRelation, and RelationBuildPartitionDesc). Therefore, even
65 : : * though we hand back a direct pointer into the relcache entry, it's safe
66 : : * for callers to continue to use that pointer as long as (a) they hold the
67 : : * relation open, and (b) they hold a relation lock strong enough to ensure
68 : : * that the data doesn't become stale.
69 : : */
70 : : PartitionDesc
1598 alvherre@alvh.no-ip. 71 :CBC 31042 : RelationGetPartitionDesc(Relation rel, bool omit_detached)
72 : : {
73 [ - + ]: 31042 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
74 : :
75 : : /*
76 : : * If relcache has a partition descriptor, use that. However, we can only
77 : : * do so when we are asked to include all partitions including detached;
78 : : * and also when we know that there are no detached partitions.
79 : : *
80 : : * If there is no active snapshot, detached partitions aren't omitted
81 : : * either, so we can use the cached descriptor too in that case.
82 : : */
83 [ + + + + : 31042 : if (likely(rel->rd_partdesc &&
+ + - + +
+ ]
84 : : (!rel->rd_partdesc->detached_exist || !omit_detached ||
85 : : !ActiveSnapshotSet())))
86 : 19877 : return rel->rd_partdesc;
87 : :
88 : : /*
89 : : * If we're asked to omit detached partitions, we may be able to use a
90 : : * cached descriptor too. We determine that based on the pg_inherits.xmin
91 : : * that was saved alongside that descriptor: if the xmin that was not in
92 : : * progress for that active snapshot is also not in progress for the
93 : : * current active snapshot, then we can use it. Otherwise build one from
94 : : * scratch.
95 : : */
1592 96 [ + + ]: 11165 : if (omit_detached &&
97 [ + + + - ]: 10888 : rel->rd_partdesc_nodetached &&
98 : 7 : ActiveSnapshotSet())
99 : : {
100 : : Snapshot activesnap;
101 : :
1584 102 [ - + ]: 7 : Assert(TransactionIdIsValid(rel->rd_partdesc_nodetached_xmin));
1592 103 : 7 : activesnap = GetActiveSnapshot();
104 : :
105 [ + - ]: 7 : if (!XidInMVCCSnapshot(rel->rd_partdesc_nodetached_xmin, activesnap))
106 : 7 : return rel->rd_partdesc_nodetached;
107 : : }
108 : :
1598 109 : 11158 : return RelationBuildPartitionDesc(rel, omit_detached);
110 : : }
111 : :
112 : : /*
113 : : * RelationBuildPartitionDesc
114 : : * Form rel's partition descriptor, and store in relcache entry
115 : : *
116 : : * Partition descriptor is a complex structure; to avoid complicated logic to
117 : : * free individual elements whenever the relcache entry is flushed, we give it
118 : : * its own memory context, a child of CacheMemoryContext, which can easily be
119 : : * deleted on its own. To avoid leaking memory in that context in case of an
120 : : * error partway through this function, the context is initially created as a
121 : : * child of CurTransactionContext and only re-parented to CacheMemoryContext
122 : : * at the end, when no further errors are possible. Also, we don't make this
123 : : * context the current context except in very brief code sections, out of fear
124 : : * that some of our callees allocate memory on their own which would be leaked
125 : : * permanently.
126 : : *
127 : : * As a special case, partition descriptors that are requested to omit
128 : : * partitions being detached (and which contain such partitions) are transient
129 : : * and are not associated with the relcache entry. Such descriptors only last
130 : : * through the requesting Portal, so we use the corresponding memory context
131 : : * for them.
132 : : */
133 : : static PartitionDesc
134 : 11158 : RelationBuildPartitionDesc(Relation rel, bool omit_detached)
135 : : {
136 : : PartitionDesc partdesc;
2389 rhaas@postgresql.org 137 : 11158 : PartitionBoundInfo boundinfo = NULL;
138 : : List *inhoids;
139 : 11158 : PartitionBoundSpec **boundspecs = NULL;
140 : 11158 : Oid *oids = NULL;
2082 tgl@sss.pgh.pa.us 141 : 11158 : bool *is_leaf = NULL;
142 : : bool detached_exist;
143 : : bool is_omit;
144 : : TransactionId detached_xmin;
145 : : ListCell *cell;
146 : : int i,
147 : : nparts;
452 alvherre@alvh.no-ip. 148 : 11158 : bool retried = false;
2389 rhaas@postgresql.org 149 : 11158 : PartitionKey key = RelationGetPartitionKey(rel);
150 : : MemoryContext new_pdcxt;
151 : : MemoryContext oldcxt;
152 : : int *mapping;
153 : :
452 alvherre@alvh.no-ip. 154 : 11158 : retry:
155 : :
156 : : /*
157 : : * Get partition oids from pg_inherits. This uses a single snapshot to
158 : : * fetch the list of children, so while more children may be getting added
159 : : * or removed concurrently, whatever this function returns will be
160 : : * accurate as of some well-defined point in time.
161 : : */
1598 162 : 11158 : detached_exist = false;
1592 163 : 11158 : detached_xmin = InvalidTransactionId;
164 : 11158 : inhoids = find_inheritance_children_extended(RelationGetRelid(rel),
165 : : omit_detached, NoLock,
166 : : &detached_exist,
167 : : &detached_xmin);
168 : :
2389 rhaas@postgresql.org 169 : 11158 : nparts = list_length(inhoids);
170 : :
171 : : /* Allocate working arrays for OIDs, leaf flags, and boundspecs. */
172 [ + + ]: 11158 : if (nparts > 0)
173 : : {
2082 tgl@sss.pgh.pa.us 174 : 8192 : oids = (Oid *) palloc(nparts * sizeof(Oid));
175 : 8192 : is_leaf = (bool *) palloc(nparts * sizeof(bool));
2389 rhaas@postgresql.org 176 : 8192 : boundspecs = palloc(nparts * sizeof(PartitionBoundSpec *));
177 : : }
178 : :
179 : : /* Collect bound spec nodes for each partition. */
180 : 11158 : i = 0;
181 [ + + + + : 27433 : foreach(cell, inhoids)
+ + ]
182 : : {
183 : 16275 : Oid inhrelid = lfirst_oid(cell);
184 : : HeapTuple tuple;
2375 185 : 16275 : PartitionBoundSpec *boundspec = NULL;
186 : :
187 : : /* Try fetching the tuple from the catcache, for speed. */
779 michael@paquier.xyz 188 : 16275 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(inhrelid));
2375 rhaas@postgresql.org 189 [ + - ]: 16275 : if (HeapTupleIsValid(tuple))
190 : : {
191 : : Datum datum;
192 : : bool isnull;
193 : :
194 : 16275 : datum = SysCacheGetAttr(RELOID, tuple,
195 : : Anum_pg_class_relpartbound,
196 : : &isnull);
197 [ + - ]: 16275 : if (!isnull)
198 : 16275 : boundspec = stringToNode(TextDatumGetCString(datum));
199 : 16275 : ReleaseSysCache(tuple);
200 : : }
201 : :
202 : : /*
203 : : * Two problems are possible here. First, a concurrent ATTACH
204 : : * PARTITION might be in the process of adding a new partition, but
205 : : * the syscache doesn't have it, or its copy of it does not yet have
206 : : * its relpartbound set. We cannot just AcceptInvalidationMessages(),
207 : : * because the other process might have already removed itself from
208 : : * the ProcArray but not yet added its invalidation messages to the
209 : : * shared queue. We solve this problem by reading pg_class directly
210 : : * for the desired tuple.
211 : : *
212 : : * If the partition recently detached is also dropped, we get no tuple
213 : : * from the scan. In that case, we also retry, and next time through
214 : : * here, we don't see that partition anymore.
215 : : *
216 : : * The other problem is that DETACH CONCURRENTLY is in the process of
217 : : * removing a partition, which happens in two steps: first it marks it
218 : : * as "detach pending", commits, then unsets relpartbound. If
219 : : * find_inheritance_children_extended included that partition but we
220 : : * below we see that DETACH CONCURRENTLY has reset relpartbound for
221 : : * it, we'd see an inconsistent view. (The inconsistency is seen
222 : : * because table_open below reads invalidation messages.) We protect
223 : : * against this by retrying find_inheritance_children_extended().
224 : : */
225 [ - + ]: 16275 : if (boundspec == NULL)
226 : : {
227 : : Relation pg_class;
228 : : SysScanDesc scan;
229 : : ScanKeyData key[1];
230 : :
2375 rhaas@postgresql.org 231 :UBC 0 : pg_class = table_open(RelationRelationId, AccessShareLock);
232 : 0 : ScanKeyInit(&key[0],
233 : : Anum_pg_class_oid,
234 : : BTEqualStrategyNumber, F_OIDEQ,
235 : : ObjectIdGetDatum(inhrelid));
236 : 0 : scan = systable_beginscan(pg_class, ClassOidIndexId, true,
237 : : NULL, 1, key);
238 : :
239 : : /*
240 : : * We could get one tuple from the scan (the normal case), or zero
241 : : * tuples if the table has been dropped meanwhile.
242 : : */
243 : 0 : tuple = systable_getnext(scan);
390 alvherre@alvh.no-ip. 244 [ # # ]: 0 : if (HeapTupleIsValid(tuple))
245 : : {
246 : : Datum datum;
247 : : bool isnull;
248 : :
249 : 0 : datum = heap_getattr(tuple, Anum_pg_class_relpartbound,
250 : : RelationGetDescr(pg_class), &isnull);
251 [ # # ]: 0 : if (!isnull)
252 : 0 : boundspec = stringToNode(TextDatumGetCString(datum));
253 : : }
2375 rhaas@postgresql.org 254 : 0 : systable_endscan(scan);
255 : 0 : table_close(pg_class, AccessShareLock);
256 : :
257 : : /*
258 : : * If we still don't get a relpartbound value (either because
259 : : * boundspec is null or because there was no tuple), then it must
260 : : * be because of DETACH CONCURRENTLY. Restart from the top, as
261 : : * explained above. We only do this once, for two reasons: first,
262 : : * only one DETACH CONCURRENTLY session could affect us at a time,
263 : : * since each of them would have to wait for the snapshot under
264 : : * which this is running; and second, to avoid possible infinite
265 : : * loops in case of catalog corruption.
266 : : *
267 : : * Note that the current memory context is short-lived enough, so
268 : : * we needn't worry about memory leaks here.
269 : : */
452 alvherre@alvh.no-ip. 270 [ # # # # ]: 0 : if (!boundspec && !retried)
271 : : {
272 : 0 : AcceptInvalidationMessages();
273 : 0 : retried = true;
274 : 0 : goto retry;
275 : : }
276 : : }
277 : :
278 : : /* Sanity checks. */
2375 rhaas@postgresql.org 279 [ - + ]:CBC 16275 : if (!boundspec)
2375 rhaas@postgresql.org 280 [ # # ]:UBC 0 : elog(ERROR, "missing relpartbound for relation %u", inhrelid);
2389 rhaas@postgresql.org 281 [ - + ]:CBC 16275 : if (!IsA(boundspec, PartitionBoundSpec))
2389 rhaas@postgresql.org 282 [ # # ]:UBC 0 : elog(ERROR, "invalid relpartbound for relation %u", inhrelid);
283 : :
284 : : /*
285 : : * If the PartitionBoundSpec says this is the default partition, its
286 : : * OID should match pg_partitioned_table.partdefid; if not, the
287 : : * catalog is corrupt.
288 : : */
2389 rhaas@postgresql.org 289 [ + + ]:CBC 16275 : if (boundspec->is_default)
290 : : {
291 : : Oid partdefid;
292 : :
293 : 836 : partdefid = get_default_partition_oid(RelationGetRelid(rel));
294 [ - + ]: 836 : if (partdefid != inhrelid)
2389 rhaas@postgresql.org 295 [ # # ]:UBC 0 : elog(ERROR, "expected partdefid %u, but got %u",
296 : : inhrelid, partdefid);
297 : : }
298 : :
299 : : /* Save results. */
2389 rhaas@postgresql.org 300 :CBC 16275 : oids[i] = inhrelid;
2082 tgl@sss.pgh.pa.us 301 : 16275 : is_leaf[i] = (get_rel_relkind(inhrelid) != RELKIND_PARTITIONED_TABLE);
2389 rhaas@postgresql.org 302 : 16275 : boundspecs[i] = boundspec;
303 : 16275 : ++i;
304 : : }
305 : :
306 : : /*
307 : : * Create PartitionBoundInfo and mapping, working in the caller's context.
308 : : * This could fail, but we haven't done any damage if so.
309 : : */
2082 tgl@sss.pgh.pa.us 310 [ + + ]: 11158 : if (nparts > 0)
311 : 8192 : boundinfo = partition_bounds_create(boundspecs, nparts, key, &mapping);
312 : :
313 : : /*
314 : : * Now build the actual relcache partition descriptor, copying all the
315 : : * data into a new, small context. As per above comment, we don't make
316 : : * this a long-lived context until it's finished.
317 : : */
318 : 11158 : new_pdcxt = AllocSetContextCreate(CurTransactionContext,
319 : : "partition descriptor",
320 : : ALLOCSET_SMALL_SIZES);
321 : 11158 : MemoryContextCopyAndSetIdentifier(new_pdcxt,
322 : : RelationGetRelationName(rel));
323 : :
324 : : partdesc = (PartitionDescData *)
325 : 11158 : MemoryContextAllocZero(new_pdcxt, sizeof(PartitionDescData));
2389 rhaas@postgresql.org 326 : 11158 : partdesc->nparts = nparts;
1598 alvherre@alvh.no-ip. 327 : 11158 : partdesc->detached_exist = detached_exist;
328 : : /* If there are no partitions, the rest of the partdesc can stay zero */
2368 tgl@sss.pgh.pa.us 329 [ + + ]: 11158 : if (nparts > 0)
330 : : {
2082 331 : 8192 : oldcxt = MemoryContextSwitchTo(new_pdcxt);
2368 332 : 8192 : partdesc->boundinfo = partition_bounds_copy(boundinfo, key);
333 : :
334 : : /* Initialize caching fields for speeding up ExecFindPartition */
1131 drowley@postgresql.o 335 : 8192 : partdesc->last_found_datum_index = -1;
336 : 8192 : partdesc->last_found_part_index = -1;
337 : 8192 : partdesc->last_found_count = 0;
338 : :
2368 tgl@sss.pgh.pa.us 339 : 8192 : partdesc->oids = (Oid *) palloc(nparts * sizeof(Oid));
340 : 8192 : partdesc->is_leaf = (bool *) palloc(nparts * sizeof(bool));
341 : :
342 : : /*
343 : : * Assign OIDs from the original array into mapped indexes of the
344 : : * result array. The order of OIDs in the former is defined by the
345 : : * catalog scan that retrieved them, whereas that in the latter is
346 : : * defined by canonicalized representation of the partition bounds.
347 : : * Also save leaf-ness of each partition.
348 : : */
349 [ + + ]: 24467 : for (i = 0; i < nparts; i++)
350 : : {
351 : 16275 : int index = mapping[i];
352 : :
353 : 16275 : partdesc->oids[index] = oids[i];
2082 354 : 16275 : partdesc->is_leaf[index] = is_leaf[i];
355 : : }
356 : 8192 : MemoryContextSwitchTo(oldcxt);
357 : : }
358 : :
359 : : /*
360 : : * Are we working with the partdesc that omits the detached partition, or
361 : : * the one that includes it?
362 : : *
363 : : * Note that if a partition was found by the catalog's scan to have been
364 : : * detached, but the pg_inherit tuple saying so was not visible to the
365 : : * active snapshot (find_inheritance_children_extended will not have set
366 : : * detached_xmin in that case), we consider there to be no "omittable"
367 : : * detached partitions.
368 : : */
1584 alvherre@alvh.no-ip. 369 [ + + + + : 11203 : is_omit = omit_detached && detached_exist && ActiveSnapshotSet() &&
+ - ]
370 [ + + ]: 45 : TransactionIdIsValid(detached_xmin);
371 : :
372 : : /*
373 : : * We have a fully valid partdesc. Reparent it so that it has the right
374 : : * lifespan.
375 : : */
1592 376 : 11158 : MemoryContextSetParent(new_pdcxt, CacheMemoryContext);
377 : :
378 : : /*
379 : : * Store it into relcache.
380 : : *
381 : : * But first, a kluge: if there's an old context for this type of
382 : : * descriptor, it contains an old partition descriptor that may still be
383 : : * referenced somewhere. Preserve it, while not leaking it, by
384 : : * reattaching it as a child context of the new one. Eventually it will
385 : : * get dropped by either RelationClose or RelationClearRelation. (We keep
386 : : * the regular partdesc in rd_pdcxt, and the partdesc-excluding-
387 : : * detached-partitions in rd_pddcxt.)
388 : : */
389 [ + + ]: 11158 : if (is_omit)
390 : : {
391 [ - + ]: 33 : if (rel->rd_pddcxt != NULL)
1592 alvherre@alvh.no-ip. 392 :UBC 0 : MemoryContextSetParent(rel->rd_pddcxt, new_pdcxt);
1592 alvherre@alvh.no-ip. 393 :CBC 33 : rel->rd_pddcxt = new_pdcxt;
394 : 33 : rel->rd_partdesc_nodetached = partdesc;
395 : :
396 : : /*
397 : : * For partdescs built excluding detached partitions, which we save
398 : : * separately, we also record the pg_inherits.xmin of the detached
399 : : * partition that was omitted; this informs a future potential user of
400 : : * such a cached partdesc to only use it after cross-checking that the
401 : : * xmin is indeed visible to the snapshot it is going to be working
402 : : * with.
403 : : */
1584 404 [ - + ]: 33 : Assert(TransactionIdIsValid(detached_xmin));
1592 405 : 33 : rel->rd_partdesc_nodetached_xmin = detached_xmin;
406 : : }
407 : : else
408 : : {
1598 409 [ + + ]: 11125 : if (rel->rd_pdcxt != NULL)
410 : 2527 : MemoryContextSetParent(rel->rd_pdcxt, new_pdcxt);
411 : 11125 : rel->rd_pdcxt = new_pdcxt;
412 : 11125 : rel->rd_partdesc = partdesc;
413 : : }
414 : :
415 : 11158 : return partdesc;
416 : : }
417 : :
418 : : /*
419 : : * CreatePartitionDirectory
420 : : * Create a new partition directory object.
421 : : */
422 : : PartitionDirectory
423 : 9697 : CreatePartitionDirectory(MemoryContext mcxt, bool omit_detached)
424 : : {
2375 rhaas@postgresql.org 425 : 9697 : MemoryContext oldcontext = MemoryContextSwitchTo(mcxt);
426 : : PartitionDirectory pdir;
427 : : HASHCTL ctl;
428 : :
1726 tgl@sss.pgh.pa.us 429 : 9697 : pdir = palloc(sizeof(PartitionDirectoryData));
430 : 9697 : pdir->pdir_mcxt = mcxt;
431 : :
2375 rhaas@postgresql.org 432 : 9697 : ctl.keysize = sizeof(Oid);
433 : 9697 : ctl.entrysize = sizeof(PartitionDirectoryEntry);
434 : 9697 : ctl.hcxt = mcxt;
435 : :
436 : 9697 : pdir->pdir_hash = hash_create("partition directory", 256, &ctl,
437 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
1598 alvherre@alvh.no-ip. 438 : 9697 : pdir->omit_detached = omit_detached;
439 : :
2375 rhaas@postgresql.org 440 : 9697 : MemoryContextSwitchTo(oldcontext);
441 : 9697 : return pdir;
442 : : }
443 : :
444 : : /*
445 : : * PartitionDirectoryLookup
446 : : * Look up the partition descriptor for a relation in the directory.
447 : : *
448 : : * The purpose of this function is to ensure that we get the same
449 : : * PartitionDesc for each relation every time we look it up. In the
450 : : * face of concurrent DDL, different PartitionDescs may be constructed with
451 : : * different views of the catalog state, but any single particular OID
452 : : * will always get the same PartitionDesc for as long as the same
453 : : * PartitionDirectory is used.
454 : : */
455 : : PartitionDesc
456 : 22170 : PartitionDirectoryLookup(PartitionDirectory pdir, Relation rel)
457 : : {
458 : : PartitionDirectoryEntry *pde;
459 : 22170 : Oid relid = RelationGetRelid(rel);
460 : : bool found;
461 : :
462 : 22170 : pde = hash_search(pdir->pdir_hash, &relid, HASH_ENTER, &found);
463 [ + + ]: 22170 : if (!found)
464 : : {
465 : : /*
466 : : * We must keep a reference count on the relation so that the
467 : : * PartitionDesc to which we are pointing can't get destroyed.
468 : : */
469 : 13096 : RelationIncrementReferenceCount(rel);
470 : 13096 : pde->rel = rel;
1598 alvherre@alvh.no-ip. 471 : 13096 : pde->pd = RelationGetPartitionDesc(rel, pdir->omit_detached);
2375 rhaas@postgresql.org 472 [ - + ]: 13096 : Assert(pde->pd != NULL);
473 : : }
474 : 22170 : return pde->pd;
475 : : }
476 : :
477 : : /*
478 : : * DestroyPartitionDirectory
479 : : * Destroy a partition directory.
480 : : *
481 : : * Release the reference counts we're holding.
482 : : */
483 : : void
484 : 9288 : DestroyPartitionDirectory(PartitionDirectory pdir)
485 : : {
486 : : HASH_SEQ_STATUS status;
487 : : PartitionDirectoryEntry *pde;
488 : :
489 : 9288 : hash_seq_init(&status, pdir->pdir_hash);
490 [ + + ]: 21863 : while ((pde = hash_seq_search(&status)) != NULL)
491 : 12575 : RelationDecrementReferenceCount(pde->rel);
492 : 9288 : }
493 : :
494 : : /*
495 : : * get_default_oid_from_partdesc
496 : : *
497 : : * Given a partition descriptor, return the OID of the default partition, if
498 : : * one exists; else, return InvalidOid.
499 : : */
500 : : Oid
2389 501 : 10317 : get_default_oid_from_partdesc(PartitionDesc partdesc)
502 : : {
503 [ + - + + ]: 10317 : if (partdesc && partdesc->boundinfo &&
504 [ + + ]: 6161 : partition_bound_has_default(partdesc->boundinfo))
505 : 656 : return partdesc->oids[partdesc->boundinfo->default_index];
506 : :
507 : 9661 : return InvalidOid;
508 : : }
|