LCOV - code coverage report
Current view: top level - src/backend/utils/cache - partcache.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.4 % 131 125
Test Date: 2026-03-10 05:14:54 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * partcache.c
       4              :  *      Support routines for manipulating partition information cached in
       5              :  *      relcache
       6              :  *
       7              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8              :  * Portions Copyright (c) 1994, Regents of the University of California
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *        src/backend/utils/cache/partcache.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              : */
      15              : #include "postgres.h"
      16              : 
      17              : #include "access/hash.h"
      18              : #include "access/htup_details.h"
      19              : #include "access/nbtree.h"
      20              : #include "access/relation.h"
      21              : #include "catalog/partition.h"
      22              : #include "catalog/pg_opclass.h"
      23              : #include "catalog/pg_partitioned_table.h"
      24              : #include "miscadmin.h"
      25              : #include "nodes/makefuncs.h"
      26              : #include "nodes/nodeFuncs.h"
      27              : #include "optimizer/optimizer.h"
      28              : #include "partitioning/partbounds.h"
      29              : #include "utils/builtins.h"
      30              : #include "utils/lsyscache.h"
      31              : #include "utils/memutils.h"
      32              : #include "utils/partcache.h"
      33              : #include "utils/rel.h"
      34              : #include "utils/syscache.h"
      35              : 
      36              : 
      37              : static void RelationBuildPartitionKey(Relation relation);
      38              : static List *generate_partition_qual(Relation rel);
      39              : 
      40              : /*
      41              :  * RelationGetPartitionKey -- get partition key, if relation is partitioned
      42              :  *
      43              :  * Note: partition keys are not allowed to change after the partitioned rel
      44              :  * is created.  RelationClearRelation knows this and preserves rd_partkey
      45              :  * across relcache rebuilds, as long as the relation is open.  Therefore,
      46              :  * even though we hand back a direct pointer into the relcache entry, it's
      47              :  * safe for callers to continue to use that pointer as long as they hold
      48              :  * the relation open.
      49              :  */
      50              : PartitionKey
      51        64579 : RelationGetPartitionKey(Relation rel)
      52              : {
      53        64579 :     if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
      54            6 :         return NULL;
      55              : 
      56        64573 :     if (unlikely(rel->rd_partkey == NULL))
      57         9984 :         RelationBuildPartitionKey(rel);
      58              : 
      59        64573 :     return rel->rd_partkey;
      60              : }
      61              : 
      62              : /*
      63              :  * RelationBuildPartitionKey
      64              :  *      Build partition key data of relation, and attach to relcache
      65              :  *
      66              :  * Partitioning key data is a complex structure; to avoid complicated logic to
      67              :  * free individual elements whenever the relcache entry is flushed, we give it
      68              :  * its own memory context, a child of CacheMemoryContext, which can easily be
      69              :  * deleted on its own.  To avoid leaking memory in that context in case of an
      70              :  * error partway through this function, the context is initially created as a
      71              :  * child of CurTransactionContext and only re-parented to CacheMemoryContext
      72              :  * at the end, when no further errors are possible.  Also, we don't make this
      73              :  * context the current context except in very brief code sections, out of fear
      74              :  * that some of our callees allocate memory on their own which would be leaked
      75              :  * permanently.
      76              :  */
      77              : static void
      78         9984 : RelationBuildPartitionKey(Relation relation)
      79              : {
      80              :     Form_pg_partitioned_table form;
      81              :     HeapTuple   tuple;
      82              :     bool        isnull;
      83              :     int         i;
      84              :     PartitionKey key;
      85              :     AttrNumber *attrs;
      86              :     oidvector  *opclass;
      87              :     oidvector  *collation;
      88              :     ListCell   *partexprs_item;
      89              :     Datum       datum;
      90              :     MemoryContext partkeycxt,
      91              :                 oldcxt;
      92              :     int16       procnum;
      93              : 
      94         9984 :     tuple = SearchSysCache1(PARTRELID,
      95              :                             ObjectIdGetDatum(RelationGetRelid(relation)));
      96              : 
      97         9984 :     if (!HeapTupleIsValid(tuple))
      98            0 :         elog(ERROR, "cache lookup failed for partition key of relation %u",
      99              :              RelationGetRelid(relation));
     100              : 
     101         9984 :     partkeycxt = AllocSetContextCreate(CurTransactionContext,
     102              :                                        "partition key",
     103              :                                        ALLOCSET_SMALL_SIZES);
     104         9984 :     MemoryContextCopyAndSetIdentifier(partkeycxt,
     105              :                                       RelationGetRelationName(relation));
     106              : 
     107         9984 :     key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
     108              :                                                 sizeof(PartitionKeyData));
     109              : 
     110              :     /* Fixed-length attributes */
     111         9984 :     form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
     112         9984 :     key->strategy = form->partstrat;
     113         9984 :     key->partnatts = form->partnatts;
     114              : 
     115              :     /* Validate partition strategy code */
     116         9984 :     if (key->strategy != PARTITION_STRATEGY_LIST &&
     117         5572 :         key->strategy != PARTITION_STRATEGY_RANGE &&
     118          502 :         key->strategy != PARTITION_STRATEGY_HASH)
     119            0 :         elog(ERROR, "invalid partition strategy \"%c\"", key->strategy);
     120              : 
     121              :     /*
     122              :      * We can rely on the first variable-length attribute being mapped to the
     123              :      * relevant field of the catalog's C struct, because all previous
     124              :      * attributes are non-nullable and fixed-length.
     125              :      */
     126         9984 :     attrs = form->partattrs.values;
     127              : 
     128              :     /* But use the hard way to retrieve further variable-length attributes */
     129              :     /* Operator class */
     130         9984 :     datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
     131              :                                    Anum_pg_partitioned_table_partclass);
     132         9984 :     opclass = (oidvector *) DatumGetPointer(datum);
     133              : 
     134              :     /* Collation */
     135         9984 :     datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
     136              :                                    Anum_pg_partitioned_table_partcollation);
     137         9984 :     collation = (oidvector *) DatumGetPointer(datum);
     138              : 
     139              :     /* Expressions */
     140         9984 :     datum = SysCacheGetAttr(PARTRELID, tuple,
     141              :                             Anum_pg_partitioned_table_partexprs, &isnull);
     142         9984 :     if (!isnull)
     143              :     {
     144              :         char       *exprString;
     145              :         Node       *expr;
     146              : 
     147          499 :         exprString = TextDatumGetCString(datum);
     148          499 :         expr = stringToNode(exprString);
     149          499 :         pfree(exprString);
     150              : 
     151              :         /*
     152              :          * Run the expressions through const-simplification since the planner
     153              :          * will be comparing them to similarly-processed qual clause operands,
     154              :          * and may fail to detect valid matches without this step; fix
     155              :          * opfuncids while at it.  We don't need to bother with
     156              :          * canonicalize_qual() though, because partition expressions should be
     157              :          * in canonical form already (ie, no need for OR-merging or constant
     158              :          * elimination).
     159              :          */
     160          499 :         expr = eval_const_expressions(NULL, expr);
     161          499 :         fix_opfuncids(expr);
     162              : 
     163          499 :         oldcxt = MemoryContextSwitchTo(partkeycxt);
     164          499 :         key->partexprs = (List *) copyObject(expr);
     165          499 :         MemoryContextSwitchTo(oldcxt);
     166              :     }
     167              : 
     168              :     /* Allocate assorted arrays in the partkeycxt, which we'll fill below */
     169         9984 :     oldcxt = MemoryContextSwitchTo(partkeycxt);
     170         9984 :     key->partattrs = palloc0_array(AttrNumber, key->partnatts);
     171         9984 :     key->partopfamily = palloc0_array(Oid, key->partnatts);
     172         9984 :     key->partopcintype = palloc0_array(Oid, key->partnatts);
     173         9984 :     key->partsupfunc = palloc0_array(FmgrInfo, key->partnatts);
     174              : 
     175         9984 :     key->partcollation = palloc0_array(Oid, key->partnatts);
     176         9984 :     key->parttypid = palloc0_array(Oid, key->partnatts);
     177         9984 :     key->parttypmod = palloc0_array(int32, key->partnatts);
     178         9984 :     key->parttyplen = palloc0_array(int16, key->partnatts);
     179         9984 :     key->parttypbyval = palloc0_array(bool, key->partnatts);
     180         9984 :     key->parttypalign = palloc0_array(char, key->partnatts);
     181         9984 :     key->parttypcoll = palloc0_array(Oid, key->partnatts);
     182         9984 :     MemoryContextSwitchTo(oldcxt);
     183              : 
     184              :     /* determine support function number to search for */
     185         9984 :     procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
     186              :         HASHEXTENDED_PROC : BTORDER_PROC;
     187              : 
     188              :     /* Copy partattrs and fill other per-attribute info */
     189         9984 :     memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
     190         9984 :     partexprs_item = list_head(key->partexprs);
     191        21010 :     for (i = 0; i < key->partnatts; i++)
     192              :     {
     193        11026 :         AttrNumber  attno = key->partattrs[i];
     194              :         HeapTuple   opclasstup;
     195              :         Form_pg_opclass opclassform;
     196              :         Oid         funcid;
     197              : 
     198              :         /* Collect opfamily information */
     199        11026 :         opclasstup = SearchSysCache1(CLAOID,
     200              :                                      ObjectIdGetDatum(opclass->values[i]));
     201        11026 :         if (!HeapTupleIsValid(opclasstup))
     202            0 :             elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
     203              : 
     204        11026 :         opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
     205        11026 :         key->partopfamily[i] = opclassform->opcfamily;
     206        11026 :         key->partopcintype[i] = opclassform->opcintype;
     207              : 
     208              :         /* Get a support function for the specified opfamily and datatypes */
     209        11026 :         funcid = get_opfamily_proc(opclassform->opcfamily,
     210              :                                    opclassform->opcintype,
     211              :                                    opclassform->opcintype,
     212              :                                    procnum);
     213        11026 :         if (!OidIsValid(funcid))
     214            0 :             ereport(ERROR,
     215              :                     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     216              :                      errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
     217              :                             NameStr(opclassform->opcname),
     218              :                             (key->strategy == PARTITION_STRATEGY_HASH) ?
     219              :                             "hash" : "btree",
     220              :                             procnum,
     221              :                             format_type_be(opclassform->opcintype))));
     222              : 
     223        11026 :         fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
     224              : 
     225              :         /* Collation */
     226        11026 :         key->partcollation[i] = collation->values[i];
     227              : 
     228              :         /* Collect type information */
     229        11026 :         if (attno != 0)
     230              :         {
     231        10491 :             Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
     232              : 
     233        10491 :             key->parttypid[i] = att->atttypid;
     234        10491 :             key->parttypmod[i] = att->atttypmod;
     235        10491 :             key->parttypcoll[i] = att->attcollation;
     236              :         }
     237              :         else
     238              :         {
     239          535 :             if (partexprs_item == NULL)
     240            0 :                 elog(ERROR, "wrong number of partition key expressions");
     241              : 
     242          535 :             key->parttypid[i] = exprType(lfirst(partexprs_item));
     243          535 :             key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
     244          535 :             key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
     245              : 
     246          535 :             partexprs_item = lnext(key->partexprs, partexprs_item);
     247              :         }
     248        11026 :         get_typlenbyvalalign(key->parttypid[i],
     249        11026 :                              &key->parttyplen[i],
     250        11026 :                              &key->parttypbyval[i],
     251        11026 :                              &key->parttypalign[i]);
     252              : 
     253        11026 :         ReleaseSysCache(opclasstup);
     254              :     }
     255              : 
     256         9984 :     ReleaseSysCache(tuple);
     257              : 
     258              :     /* Assert that we're not leaking any old data during assignments below */
     259              :     Assert(relation->rd_partkeycxt == NULL);
     260              :     Assert(relation->rd_partkey == NULL);
     261              : 
     262              :     /*
     263              :      * Success --- reparent our context and make the relcache point to the
     264              :      * newly constructed key
     265              :      */
     266         9984 :     MemoryContextSetParent(partkeycxt, CacheMemoryContext);
     267         9984 :     relation->rd_partkeycxt = partkeycxt;
     268         9984 :     relation->rd_partkey = key;
     269         9984 : }
     270              : 
     271              : /*
     272              :  * RelationGetPartitionQual
     273              :  *
     274              :  * Returns a list of partition quals
     275              :  */
     276              : List *
     277        13094 : RelationGetPartitionQual(Relation rel)
     278              : {
     279              :     /* Quick exit */
     280        13094 :     if (!rel->rd_rel->relispartition)
     281         8173 :         return NIL;
     282              : 
     283         4921 :     return generate_partition_qual(rel);
     284              : }
     285              : 
     286              : /*
     287              :  * get_partition_qual_relid
     288              :  *
     289              :  * Returns an expression tree describing the passed-in relation's partition
     290              :  * constraint.
     291              :  *
     292              :  * If the relation is not found, or is not a partition, or there is no
     293              :  * partition constraint, return NULL.  We must guard against the first two
     294              :  * cases because this supports a SQL function that could be passed any OID.
     295              :  * The last case can happen even if relispartition is true, when a default
     296              :  * partition is the only partition.
     297              :  */
     298              : Expr *
     299          170 : get_partition_qual_relid(Oid relid)
     300              : {
     301          170 :     Expr       *result = NULL;
     302              : 
     303              :     /* Do the work only if this relation exists and is a partition. */
     304          170 :     if (get_rel_relispartition(relid))
     305              :     {
     306          170 :         Relation    rel = relation_open(relid, AccessShareLock);
     307              :         List       *and_args;
     308              : 
     309          170 :         and_args = generate_partition_qual(rel);
     310              : 
     311              :         /* Convert implicit-AND list format to boolean expression */
     312          170 :         if (and_args == NIL)
     313           12 :             result = NULL;
     314          158 :         else if (list_length(and_args) > 1)
     315          149 :             result = makeBoolExpr(AND_EXPR, and_args, -1);
     316              :         else
     317            9 :             result = linitial(and_args);
     318              : 
     319              :         /* Keep the lock, to allow safe deparsing against the rel by caller. */
     320          170 :         relation_close(rel, NoLock);
     321              :     }
     322              : 
     323          170 :     return result;
     324              : }
     325              : 
     326              : /*
     327              :  * generate_partition_qual
     328              :  *
     329              :  * Generate partition predicate from rel's partition bound expression. The
     330              :  * function returns a NIL list if there is no predicate.
     331              :  *
     332              :  * We cache a copy of the result in the relcache entry, after constructing
     333              :  * it using the caller's context.  This approach avoids leaking any data
     334              :  * into long-lived cache contexts, especially if we fail partway through.
     335              :  */
     336              : static List *
     337         5383 : generate_partition_qual(Relation rel)
     338              : {
     339              :     HeapTuple   tuple;
     340              :     MemoryContext oldcxt;
     341              :     Datum       boundDatum;
     342              :     bool        isnull;
     343         5383 :     List       *my_qual = NIL,
     344         5383 :                *result = NIL;
     345              :     Oid         parentrelid;
     346              :     Relation    parent;
     347              : 
     348              :     /* Guard against stack overflow due to overly deep partition tree */
     349         5383 :     check_stack_depth();
     350              : 
     351              :     /* If we already cached the result, just return a copy */
     352         5383 :     if (rel->rd_partcheckvalid)
     353         3455 :         return copyObject(rel->rd_partcheck);
     354              : 
     355              :     /*
     356              :      * Grab at least an AccessShareLock on the parent table.  Must do this
     357              :      * even if the partition has been partially detached, because transactions
     358              :      * concurrent with the detach might still be trying to use a partition
     359              :      * descriptor that includes it.
     360              :      */
     361         1928 :     parentrelid = get_partition_parent(RelationGetRelid(rel), true);
     362         1928 :     parent = relation_open(parentrelid, AccessShareLock);
     363              : 
     364              :     /* Get pg_class.relpartbound */
     365         1928 :     tuple = SearchSysCache1(RELOID,
     366              :                             ObjectIdGetDatum(RelationGetRelid(rel)));
     367         1928 :     if (!HeapTupleIsValid(tuple))
     368            0 :         elog(ERROR, "cache lookup failed for relation %u",
     369              :              RelationGetRelid(rel));
     370              : 
     371         1928 :     boundDatum = SysCacheGetAttr(RELOID, tuple,
     372              :                                  Anum_pg_class_relpartbound,
     373              :                                  &isnull);
     374         1928 :     if (!isnull)
     375              :     {
     376              :         PartitionBoundSpec *bound;
     377              : 
     378         1922 :         bound = castNode(PartitionBoundSpec,
     379              :                          stringToNode(TextDatumGetCString(boundDatum)));
     380              : 
     381         1922 :         my_qual = get_qual_from_partbound(parent, bound);
     382              :     }
     383              : 
     384         1928 :     ReleaseSysCache(tuple);
     385              : 
     386              :     /* Add the parent's quals to the list (if any) */
     387         1928 :     if (parent->rd_rel->relispartition)
     388          292 :         result = list_concat(generate_partition_qual(parent), my_qual);
     389              :     else
     390         1636 :         result = my_qual;
     391              : 
     392              :     /*
     393              :      * Change Vars to have partition's attnos instead of the parent's. We do
     394              :      * this after we concatenate the parent's quals, because we want every Var
     395              :      * in it to bear this relation's attnos. It's safe to assume varno = 1
     396              :      * here.
     397              :      */
     398         1928 :     result = map_partition_varattnos(result, 1, rel, parent);
     399              : 
     400              :     /* Assert that we're not leaking any old data during assignments below */
     401              :     Assert(rel->rd_partcheckcxt == NULL);
     402              :     Assert(rel->rd_partcheck == NIL);
     403              : 
     404              :     /*
     405              :      * Save a copy in the relcache.  The order of these operations is fairly
     406              :      * critical to avoid memory leaks and ensure that we don't leave a corrupt
     407              :      * relcache entry if we fail partway through copyObject.
     408              :      *
     409              :      * If, as is definitely possible, the partcheck list is NIL, then we do
     410              :      * not need to make a context to hold it.
     411              :      */
     412         1928 :     if (result != NIL)
     413              :     {
     414         1893 :         rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
     415              :                                                      "partition constraint",
     416              :                                                      ALLOCSET_SMALL_SIZES);
     417         1893 :         MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
     418              :                                           RelationGetRelationName(rel));
     419         1893 :         oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt);
     420         1893 :         rel->rd_partcheck = copyObject(result);
     421         1893 :         MemoryContextSwitchTo(oldcxt);
     422              :     }
     423              :     else
     424           35 :         rel->rd_partcheck = NIL;
     425         1928 :     rel->rd_partcheckvalid = true;
     426              : 
     427              :     /* Keep the parent locked until commit */
     428         1928 :     relation_close(parent, NoLock);
     429              : 
     430              :     /* Return the working copy to the caller */
     431         1928 :     return result;
     432              : }
        

Generated by: LCOV version 2.0-1