PostgreSQL Source Code  git master
partdesc.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * partdesc.c
4  * Support routines for manipulating partition descriptors
5  *
6  * Portions Copyright (c) 1996-2024, 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"
23 #include "partitioning/partdesc.h"
24 #include "utils/builtins.h"
25 #include "utils/fmgroids.h"
26 #include "utils/hsearch.h"
27 #include "utils/lsyscache.h"
28 #include "utils/memutils.h"
29 #include "utils/partcache.h"
30 #include "utils/rel.h"
31 #include "utils/snapmgr.h"
32 #include "utils/syscache.h"
33 
34 typedef struct PartitionDirectoryData
35 {
40 
42 {
47 
49  bool omit_detached);
50 
51 
52 /*
53  * RelationGetPartitionDesc -- get partition descriptor, if relation is partitioned
54  *
55  * We keep two partdescs in relcache: rd_partdesc includes all partitions
56  * (even those being concurrently marked detached), while rd_partdesc_nodetached
57  * omits (some of) those. We store the pg_inherits.xmin value for the latter,
58  * to determine whether it can be validly reused in each case, since that
59  * depends on the active snapshot.
60  *
61  * Note: we arrange for partition descriptors to not get freed until the
62  * relcache entry's refcount goes to zero (see hacks in RelationClose,
63  * RelationClearRelation, and RelationBuildPartitionDesc). Therefore, even
64  * though we hand back a direct pointer into the relcache entry, it's safe
65  * for callers to continue to use that pointer as long as (a) they hold the
66  * relation open, and (b) they hold a relation lock strong enough to ensure
67  * that the data doesn't become stale.
68  */
70 RelationGetPartitionDesc(Relation rel, bool omit_detached)
71 {
72  Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
73 
74  /*
75  * If relcache has a partition descriptor, use that. However, we can only
76  * do so when we are asked to include all partitions including detached;
77  * and also when we know that there are no detached partitions.
78  *
79  * If there is no active snapshot, detached partitions aren't omitted
80  * either, so we can use the cached descriptor too in that case.
81  */
82  if (likely(rel->rd_partdesc &&
83  (!rel->rd_partdesc->detached_exist || !omit_detached ||
84  !ActiveSnapshotSet())))
85  return rel->rd_partdesc;
86 
87  /*
88  * If we're asked to omit detached partitions, we may be able to use a
89  * cached descriptor too. We determine that based on the pg_inherits.xmin
90  * that was saved alongside that descriptor: if the xmin that was not in
91  * progress for that active snapshot is also not in progress for the
92  * current active snapshot, then we can use it. Otherwise build one from
93  * scratch.
94  */
95  if (omit_detached &&
98  {
99  Snapshot activesnap;
100 
102  activesnap = GetActiveSnapshot();
103 
104  if (!XidInMVCCSnapshot(rel->rd_partdesc_nodetached_xmin, activesnap))
105  return rel->rd_partdesc_nodetached;
106  }
107 
108  return RelationBuildPartitionDesc(rel, omit_detached);
109 }
110 
111 /*
112  * RelationBuildPartitionDesc
113  * Form rel's partition descriptor, and store in relcache entry
114  *
115  * Partition descriptor is a complex structure; to avoid complicated logic to
116  * free individual elements whenever the relcache entry is flushed, we give it
117  * its own memory context, a child of CacheMemoryContext, which can easily be
118  * deleted on its own. To avoid leaking memory in that context in case of an
119  * error partway through this function, the context is initially created as a
120  * child of CurTransactionContext and only re-parented to CacheMemoryContext
121  * at the end, when no further errors are possible. Also, we don't make this
122  * context the current context except in very brief code sections, out of fear
123  * that some of our callees allocate memory on their own which would be leaked
124  * permanently.
125  *
126  * As a special case, partition descriptors that are requested to omit
127  * partitions being detached (and which contain such partitions) are transient
128  * and are not associated with the relcache entry. Such descriptors only last
129  * through the requesting Portal, so we use the corresponding memory context
130  * for them.
131  */
132 static PartitionDesc
133 RelationBuildPartitionDesc(Relation rel, bool omit_detached)
134 {
135  PartitionDesc partdesc;
136  PartitionBoundInfo boundinfo = NULL;
137  List *inhoids;
138  PartitionBoundSpec **boundspecs = NULL;
139  Oid *oids = NULL;
140  bool *is_leaf = NULL;
141  bool detached_exist;
142  bool is_omit;
143  TransactionId detached_xmin;
144  ListCell *cell;
145  int i,
146  nparts;
148  MemoryContext new_pdcxt;
149  MemoryContext oldcxt;
150  int *mapping;
151 
152  /*
153  * Get partition oids from pg_inherits. This uses a single snapshot to
154  * fetch the list of children, so while more children may be getting added
155  * concurrently, whatever this function returns will be accurate as of
156  * some well-defined point in time.
157  */
158  detached_exist = false;
159  detached_xmin = InvalidTransactionId;
161  omit_detached, NoLock,
162  &detached_exist,
163  &detached_xmin);
164 
165  nparts = list_length(inhoids);
166 
167  /* Allocate working arrays for OIDs, leaf flags, and boundspecs. */
168  if (nparts > 0)
169  {
170  oids = (Oid *) palloc(nparts * sizeof(Oid));
171  is_leaf = (bool *) palloc(nparts * sizeof(bool));
172  boundspecs = palloc(nparts * sizeof(PartitionBoundSpec *));
173  }
174 
175  /* Collect bound spec nodes for each partition. */
176  i = 0;
177  foreach(cell, inhoids)
178  {
179  Oid inhrelid = lfirst_oid(cell);
180  HeapTuple tuple;
181  PartitionBoundSpec *boundspec = NULL;
182 
183  /* Try fetching the tuple from the catcache, for speed. */
184  tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(inhrelid));
185  if (HeapTupleIsValid(tuple))
186  {
187  Datum datum;
188  bool isnull;
189 
190  datum = SysCacheGetAttr(RELOID, tuple,
191  Anum_pg_class_relpartbound,
192  &isnull);
193  if (!isnull)
194  boundspec = stringToNode(TextDatumGetCString(datum));
195  ReleaseSysCache(tuple);
196  }
197 
198  /*
199  * The system cache may be out of date; if so, we may find no pg_class
200  * tuple or an old one where relpartbound is NULL. In that case, try
201  * the table directly. We can't just AcceptInvalidationMessages() and
202  * retry the system cache lookup because it's possible that a
203  * concurrent ATTACH PARTITION operation has removed itself from the
204  * ProcArray but not yet added invalidation messages to the shared
205  * queue; InvalidateSystemCaches() would work, but seems excessive.
206  *
207  * Note that this algorithm assumes that PartitionBoundSpec we manage
208  * to fetch is the right one -- so this is only good enough for
209  * concurrent ATTACH PARTITION, not concurrent DETACH PARTITION or
210  * some hypothetical operation that changes the partition bounds.
211  */
212  if (boundspec == NULL)
213  {
214  Relation pg_class;
215  SysScanDesc scan;
216  ScanKeyData key[1];
217  Datum datum;
218  bool isnull;
219 
220  pg_class = table_open(RelationRelationId, AccessShareLock);
221  ScanKeyInit(&key[0],
222  Anum_pg_class_oid,
223  BTEqualStrategyNumber, F_OIDEQ,
224  ObjectIdGetDatum(inhrelid));
225  scan = systable_beginscan(pg_class, ClassOidIndexId, true,
226  NULL, 1, key);
227  tuple = systable_getnext(scan);
228  datum = heap_getattr(tuple, Anum_pg_class_relpartbound,
229  RelationGetDescr(pg_class), &isnull);
230  if (!isnull)
231  boundspec = stringToNode(TextDatumGetCString(datum));
232  systable_endscan(scan);
233  table_close(pg_class, AccessShareLock);
234  }
235 
236  /* Sanity checks. */
237  if (!boundspec)
238  elog(ERROR, "missing relpartbound for relation %u", inhrelid);
239  if (!IsA(boundspec, PartitionBoundSpec))
240  elog(ERROR, "invalid relpartbound for relation %u", inhrelid);
241 
242  /*
243  * If the PartitionBoundSpec says this is the default partition, its
244  * OID should match pg_partitioned_table.partdefid; if not, the
245  * catalog is corrupt.
246  */
247  if (boundspec->is_default)
248  {
249  Oid partdefid;
250 
252  if (partdefid != inhrelid)
253  elog(ERROR, "expected partdefid %u, but got %u",
254  inhrelid, partdefid);
255  }
256 
257  /* Save results. */
258  oids[i] = inhrelid;
259  is_leaf[i] = (get_rel_relkind(inhrelid) != RELKIND_PARTITIONED_TABLE);
260  boundspecs[i] = boundspec;
261  ++i;
262  }
263 
264  /*
265  * Create PartitionBoundInfo and mapping, working in the caller's context.
266  * This could fail, but we haven't done any damage if so.
267  */
268  if (nparts > 0)
269  boundinfo = partition_bounds_create(boundspecs, nparts, key, &mapping);
270 
271  /*
272  * Now build the actual relcache partition descriptor, copying all the
273  * data into a new, small context. As per above comment, we don't make
274  * this a long-lived context until it's finished.
275  */
277  "partition descriptor",
281 
282  partdesc = (PartitionDescData *)
283  MemoryContextAllocZero(new_pdcxt, sizeof(PartitionDescData));
284  partdesc->nparts = nparts;
285  partdesc->detached_exist = detached_exist;
286  /* If there are no partitions, the rest of the partdesc can stay zero */
287  if (nparts > 0)
288  {
289  oldcxt = MemoryContextSwitchTo(new_pdcxt);
290  partdesc->boundinfo = partition_bounds_copy(boundinfo, key);
291 
292  /* Initialize caching fields for speeding up ExecFindPartition */
293  partdesc->last_found_datum_index = -1;
294  partdesc->last_found_part_index = -1;
295  partdesc->last_found_count = 0;
296 
297  partdesc->oids = (Oid *) palloc(nparts * sizeof(Oid));
298  partdesc->is_leaf = (bool *) palloc(nparts * sizeof(bool));
299 
300  /*
301  * Assign OIDs from the original array into mapped indexes of the
302  * result array. The order of OIDs in the former is defined by the
303  * catalog scan that retrieved them, whereas that in the latter is
304  * defined by canonicalized representation of the partition bounds.
305  * Also save leaf-ness of each partition.
306  */
307  for (i = 0; i < nparts; i++)
308  {
309  int index = mapping[i];
310 
311  partdesc->oids[index] = oids[i];
312  partdesc->is_leaf[index] = is_leaf[i];
313  }
314  MemoryContextSwitchTo(oldcxt);
315  }
316 
317  /*
318  * Are we working with the partdesc that omits the detached partition, or
319  * the one that includes it?
320  *
321  * Note that if a partition was found by the catalog's scan to have been
322  * detached, but the pg_inherit tuple saying so was not visible to the
323  * active snapshot (find_inheritance_children_extended will not have set
324  * detached_xmin in that case), we consider there to be no "omittable"
325  * detached partitions.
326  */
327  is_omit = omit_detached && detached_exist && ActiveSnapshotSet() &&
328  TransactionIdIsValid(detached_xmin);
329 
330  /*
331  * We have a fully valid partdesc. Reparent it so that it has the right
332  * lifespan.
333  */
335 
336  /*
337  * Store it into relcache.
338  *
339  * But first, a kluge: if there's an old context for this type of
340  * descriptor, it contains an old partition descriptor that may still be
341  * referenced somewhere. Preserve it, while not leaking it, by
342  * reattaching it as a child context of the new one. Eventually it will
343  * get dropped by either RelationClose or RelationClearRelation. (We keep
344  * the regular partdesc in rd_pdcxt, and the partdesc-excluding-
345  * detached-partitions in rd_pddcxt.)
346  */
347  if (is_omit)
348  {
349  if (rel->rd_pddcxt != NULL)
350  MemoryContextSetParent(rel->rd_pddcxt, new_pdcxt);
351  rel->rd_pddcxt = new_pdcxt;
352  rel->rd_partdesc_nodetached = partdesc;
353 
354  /*
355  * For partdescs built excluding detached partitions, which we save
356  * separately, we also record the pg_inherits.xmin of the detached
357  * partition that was omitted; this informs a future potential user of
358  * such a cached partdesc to only use it after cross-checking that the
359  * xmin is indeed visible to the snapshot it is going to be working
360  * with.
361  */
362  Assert(TransactionIdIsValid(detached_xmin));
363  rel->rd_partdesc_nodetached_xmin = detached_xmin;
364  }
365  else
366  {
367  if (rel->rd_pdcxt != NULL)
368  MemoryContextSetParent(rel->rd_pdcxt, new_pdcxt);
369  rel->rd_pdcxt = new_pdcxt;
370  rel->rd_partdesc = partdesc;
371  }
372 
373  return partdesc;
374 }
375 
376 /*
377  * CreatePartitionDirectory
378  * Create a new partition directory object.
379  */
381 CreatePartitionDirectory(MemoryContext mcxt, bool omit_detached)
382 {
383  MemoryContext oldcontext = MemoryContextSwitchTo(mcxt);
384  PartitionDirectory pdir;
385  HASHCTL ctl;
386 
387  pdir = palloc(sizeof(PartitionDirectoryData));
388  pdir->pdir_mcxt = mcxt;
389 
390  ctl.keysize = sizeof(Oid);
391  ctl.entrysize = sizeof(PartitionDirectoryEntry);
392  ctl.hcxt = mcxt;
393 
394  pdir->pdir_hash = hash_create("partition directory", 256, &ctl,
396  pdir->omit_detached = omit_detached;
397 
398  MemoryContextSwitchTo(oldcontext);
399  return pdir;
400 }
401 
402 /*
403  * PartitionDirectoryLookup
404  * Look up the partition descriptor for a relation in the directory.
405  *
406  * The purpose of this function is to ensure that we get the same
407  * PartitionDesc for each relation every time we look it up. In the
408  * face of concurrent DDL, different PartitionDescs may be constructed with
409  * different views of the catalog state, but any single particular OID
410  * will always get the same PartitionDesc for as long as the same
411  * PartitionDirectory is used.
412  */
415 {
417  Oid relid = RelationGetRelid(rel);
418  bool found;
419 
420  pde = hash_search(pdir->pdir_hash, &relid, HASH_ENTER, &found);
421  if (!found)
422  {
423  /*
424  * We must keep a reference count on the relation so that the
425  * PartitionDesc to which we are pointing can't get destroyed.
426  */
428  pde->rel = rel;
429  pde->pd = RelationGetPartitionDesc(rel, pdir->omit_detached);
430  Assert(pde->pd != NULL);
431  }
432  return pde->pd;
433 }
434 
435 /*
436  * DestroyPartitionDirectory
437  * Destroy a partition directory.
438  *
439  * Release the reference counts we're holding.
440  */
441 void
443 {
444  HASH_SEQ_STATUS status;
446 
447  hash_seq_init(&status, pdir->pdir_hash);
448  while ((pde = hash_seq_search(&status)) != NULL)
450 }
451 
452 /*
453  * get_default_oid_from_partdesc
454  *
455  * Given a partition descriptor, return the OID of the default partition, if
456  * one exists; else, return InvalidOid.
457  */
458 Oid
460 {
461  if (partdesc && partdesc->boundinfo &&
463  return partdesc->oids[partdesc->boundinfo->default_index];
464 
465  return InvalidOid;
466 }
#define TextDatumGetCString(d)
Definition: builtins.h:98
#define likely(x)
Definition: c.h:297
uint32 TransactionId
Definition: c.h:639
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:955
HTAB * hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:352
void * hash_seq_search(HASH_SEQ_STATUS *status)
Definition: dynahash.c:1395
void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp)
Definition: dynahash.c:1385
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:224
void systable_endscan(SysScanDesc sysscan)
Definition: genam.c:596
HeapTuple systable_getnext(SysScanDesc sysscan)
Definition: genam.c:503
SysScanDesc systable_beginscan(Relation heapRelation, Oid indexId, bool indexOK, Snapshot snapshot, int nkeys, ScanKey key)
Definition: genam.c:384
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_CONTEXT
Definition: hsearch.h:102
#define HASH_ELEM
Definition: hsearch.h:95
#define HASH_BLOBS
Definition: hsearch.h:97
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
static Datum heap_getattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
Definition: htup_details.h:792
int i
Definition: isn.c:73
Assert(fmt[strlen(fmt) - 1] !='\n')
#define NoLock
Definition: lockdefs.h:34
#define AccessShareLock
Definition: lockdefs.h:36
char get_rel_relkind(Oid relid)
Definition: lsyscache.c:1981
void MemoryContextSetParent(MemoryContext context, MemoryContext new_parent)
Definition: mcxt.c:625
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:1202
MemoryContext CurTransactionContext
Definition: mcxt.c:143
MemoryContext CacheMemoryContext
Definition: mcxt.c:140
void * palloc(Size size)
Definition: mcxt.c:1304
#define AllocSetContextCreate
Definition: memutils.h:129
#define ALLOCSET_SMALL_SIZES
Definition: memutils.h:163
#define MemoryContextCopyAndSetIdentifier(cxt, id)
Definition: memutils.h:101
#define IsA(nodeptr, _type_)
Definition: nodes.h:158
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
PartitionBoundInfo partition_bounds_create(PartitionBoundSpec **boundspecs, int nparts, PartitionKey key, int **mapping)
Definition: partbounds.c:299
PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo src, PartitionKey key)
Definition: partbounds.c:1002
#define partition_bound_has_default(bi)
Definition: partbounds.h:99
PartitionKey RelationGetPartitionKey(Relation rel)
Definition: partcache.c:51
PartitionDirectory CreatePartitionDirectory(MemoryContext mcxt, bool omit_detached)
Definition: partdesc.c:381
void DestroyPartitionDirectory(PartitionDirectory pdir)
Definition: partdesc.c:442
PartitionDesc PartitionDirectoryLookup(PartitionDirectory pdir, Relation rel)
Definition: partdesc.c:414
PartitionDesc RelationGetPartitionDesc(Relation rel, bool omit_detached)
Definition: partdesc.c:70
static PartitionDesc RelationBuildPartitionDesc(Relation rel, bool omit_detached)
Definition: partdesc.c:133
Oid get_default_oid_from_partdesc(PartitionDesc partdesc)
Definition: partdesc.c:459
struct PartitionDirectoryEntry PartitionDirectoryEntry
struct PartitionDirectoryData PartitionDirectoryData
Oid get_default_partition_oid(Oid parentId)
Definition: partition.c:315
List * find_inheritance_children_extended(Oid parentrelId, bool omit_detached, LOCKMODE lockmode, bool *detached_exist, TransactionId *detached_xmin)
Definition: pg_inherits.c:82
static int list_length(const List *l)
Definition: pg_list.h:152
#define lfirst_oid(lc)
Definition: pg_list.h:174
uintptr_t Datum
Definition: postgres.h:64
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:252
#define InvalidOid
Definition: postgres_ext.h:36
unsigned int Oid
Definition: postgres_ext.h:31
void * stringToNode(const char *str)
Definition: read.c:90
#define RelationGetRelid(relation)
Definition: rel.h:505
#define RelationGetDescr(relation)
Definition: rel.h:531
#define RelationGetRelationName(relation)
Definition: rel.h:539
void RelationDecrementReferenceCount(Relation rel)
Definition: relcache.c:2166
void RelationIncrementReferenceCount(Relation rel)
Definition: relcache.c:2153
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Definition: scankey.c:76
bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
Definition: snapmgr.c:1856
bool ActiveSnapshotSet(void)
Definition: snapmgr.c:782
Snapshot GetActiveSnapshot(void)
Definition: snapmgr.c:770
#define BTEqualStrategyNumber
Definition: stratnum.h:31
Size keysize
Definition: hsearch.h:75
Size entrysize
Definition: hsearch.h:76
MemoryContext hcxt
Definition: hsearch.h:86
Definition: dynahash.c:220
Definition: pg_list.h:54
int last_found_datum_index
Definition: partdesc.h:46
PartitionBoundInfo boundinfo
Definition: partdesc.h:38
int last_found_count
Definition: partdesc.h:63
bool detached_exist
Definition: partdesc.h:32
bool * is_leaf
Definition: partdesc.h:35
int last_found_part_index
Definition: partdesc.h:52
MemoryContext pdir_mcxt
Definition: partdesc.c:36
PartitionDesc pd
Definition: partdesc.c:45
MemoryContext rd_pdcxt
Definition: rel.h:131
TransactionId rd_partdesc_nodetached_xmin
Definition: rel.h:144
PartitionDesc rd_partdesc
Definition: rel.h:130
PartitionDesc rd_partdesc_nodetached
Definition: rel.h:134
MemoryContext rd_pddcxt
Definition: rel.h:135
Form_pg_class rd_rel
Definition: rel.h:111
Definition: type.h:95
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:266
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:218
Datum SysCacheGetAttr(int cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition: syscache.c:479
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:126
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:40
#define InvalidTransactionId
Definition: transam.h:31
#define TransactionIdIsValid(xid)
Definition: transam.h:41