LCOV - code coverage report
Current view: top level - src/backend/catalog - partition.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 96.3 % 107 103
Test Date: 2026-02-17 17:20:33 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * partition.c
       4              :  *        Partitioning related data structures and functions.
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *        src/backend/catalog/partition.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              : */
      15              : #include "postgres.h"
      16              : 
      17              : #include "access/attmap.h"
      18              : #include "access/genam.h"
      19              : #include "access/htup_details.h"
      20              : #include "access/sysattr.h"
      21              : #include "access/table.h"
      22              : #include "catalog/indexing.h"
      23              : #include "catalog/partition.h"
      24              : #include "catalog/pg_inherits.h"
      25              : #include "catalog/pg_partitioned_table.h"
      26              : #include "nodes/makefuncs.h"
      27              : #include "optimizer/optimizer.h"
      28              : #include "rewrite/rewriteManip.h"
      29              : #include "utils/fmgroids.h"
      30              : #include "utils/partcache.h"
      31              : #include "utils/rel.h"
      32              : #include "utils/syscache.h"
      33              : 
      34              : static Oid  get_partition_parent_worker(Relation inhRel, Oid relid,
      35              :                                         bool *detach_pending);
      36              : static void get_partition_ancestors_worker(Relation inhRel, Oid relid,
      37              :                                            List **ancestors);
      38              : 
      39              : /*
      40              :  * get_partition_parent
      41              :  *      Obtain direct parent of given relation
      42              :  *
      43              :  * Returns inheritance parent of a partition by scanning pg_inherits
      44              :  *
      45              :  * If the partition is in the process of being detached, an error is thrown,
      46              :  * unless even_if_detached is passed as true.
      47              :  *
      48              :  * Note: Because this function assumes that the relation whose OID is passed
      49              :  * as an argument will have precisely one parent, it should only be called
      50              :  * when it is known that the relation is a partition.
      51              :  */
      52              : Oid
      53         7787 : get_partition_parent(Oid relid, bool even_if_detached)
      54              : {
      55              :     Relation    catalogRelation;
      56              :     Oid         result;
      57              :     bool        detach_pending;
      58              : 
      59         7787 :     catalogRelation = table_open(InheritsRelationId, AccessShareLock);
      60              : 
      61         7787 :     result = get_partition_parent_worker(catalogRelation, relid,
      62              :                                          &detach_pending);
      63              : 
      64         7787 :     if (!OidIsValid(result))
      65            0 :         elog(ERROR, "could not find tuple for parent of relation %u", relid);
      66              : 
      67         7787 :     if (detach_pending && !even_if_detached)
      68            0 :         elog(ERROR, "relation %u has no parent because it's being detached",
      69              :              relid);
      70              : 
      71         7787 :     table_close(catalogRelation, AccessShareLock);
      72              : 
      73         7787 :     return result;
      74              : }
      75              : 
      76              : /*
      77              :  * get_partition_parent_worker
      78              :  *      Scan the pg_inherits relation to return the OID of the parent of the
      79              :  *      given relation
      80              :  *
      81              :  * If the partition is being detached, *detach_pending is set true (but the
      82              :  * original parent is still returned.)
      83              :  */
      84              : static Oid
      85        17673 : get_partition_parent_worker(Relation inhRel, Oid relid, bool *detach_pending)
      86              : {
      87              :     SysScanDesc scan;
      88              :     ScanKeyData key[2];
      89        17673 :     Oid         result = InvalidOid;
      90              :     HeapTuple   tuple;
      91              : 
      92        17673 :     *detach_pending = false;
      93              : 
      94        17673 :     ScanKeyInit(&key[0],
      95              :                 Anum_pg_inherits_inhrelid,
      96              :                 BTEqualStrategyNumber, F_OIDEQ,
      97              :                 ObjectIdGetDatum(relid));
      98        17673 :     ScanKeyInit(&key[1],
      99              :                 Anum_pg_inherits_inhseqno,
     100              :                 BTEqualStrategyNumber, F_INT4EQ,
     101              :                 Int32GetDatum(1));
     102              : 
     103        17673 :     scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, true,
     104              :                               NULL, 2, key);
     105        17673 :     tuple = systable_getnext(scan);
     106        17673 :     if (HeapTupleIsValid(tuple))
     107              :     {
     108        12696 :         Form_pg_inherits form = (Form_pg_inherits) GETSTRUCT(tuple);
     109              : 
     110              :         /* Let caller know of partition being detached */
     111        12696 :         if (form->inhdetachpending)
     112           37 :             *detach_pending = true;
     113        12696 :         result = form->inhparent;
     114              :     }
     115              : 
     116        17673 :     systable_endscan(scan);
     117              : 
     118        17673 :     return result;
     119              : }
     120              : 
     121              : /*
     122              :  * get_partition_ancestors
     123              :  *      Obtain ancestors of given relation
     124              :  *
     125              :  * Returns a list of ancestors of the given relation.  The list is ordered:
     126              :  * The first element is the immediate parent and the last one is the topmost
     127              :  * parent in the partition hierarchy.
     128              :  *
     129              :  * Note: Because this function assumes that the relation whose OID is passed
     130              :  * as an argument and each ancestor will have precisely one parent, it should
     131              :  * only be called when it is known that the relation is a partition.
     132              :  */
     133              : List *
     134         4978 : get_partition_ancestors(Oid relid)
     135              : {
     136         4978 :     List       *result = NIL;
     137              :     Relation    inhRel;
     138              : 
     139         4978 :     inhRel = table_open(InheritsRelationId, AccessShareLock);
     140              : 
     141         4978 :     get_partition_ancestors_worker(inhRel, relid, &result);
     142              : 
     143         4978 :     table_close(inhRel, AccessShareLock);
     144              : 
     145         4978 :     return result;
     146              : }
     147              : 
     148              : /*
     149              :  * get_partition_ancestors_worker
     150              :  *      recursive worker for get_partition_ancestors
     151              :  */
     152              : static void
     153         9886 : get_partition_ancestors_worker(Relation inhRel, Oid relid, List **ancestors)
     154              : {
     155              :     Oid         parentOid;
     156              :     bool        detach_pending;
     157              : 
     158              :     /*
     159              :      * Recursion ends at the topmost level, ie., when there's no parent; also
     160              :      * when the partition is being detached.
     161              :      */
     162         9886 :     parentOid = get_partition_parent_worker(inhRel, relid, &detach_pending);
     163         9886 :     if (parentOid == InvalidOid || detach_pending)
     164         4978 :         return;
     165              : 
     166         4908 :     *ancestors = lappend_oid(*ancestors, parentOid);
     167         4908 :     get_partition_ancestors_worker(inhRel, parentOid, ancestors);
     168              : }
     169              : 
     170              : /*
     171              :  * index_get_partition
     172              :  *      Return the OID of index of the given partition that is a child
     173              :  *      of the given index, or InvalidOid if there isn't one.
     174              :  */
     175              : Oid
     176          798 : index_get_partition(Relation partition, Oid indexId)
     177              : {
     178          798 :     List       *idxlist = RelationGetIndexList(partition);
     179              :     ListCell   *l;
     180              : 
     181         1079 :     foreach(l, idxlist)
     182              :     {
     183          902 :         Oid         partIdx = lfirst_oid(l);
     184              :         HeapTuple   tup;
     185              :         Form_pg_class classForm;
     186              :         bool        ispartition;
     187              : 
     188          902 :         tup = SearchSysCache1(RELOID, ObjectIdGetDatum(partIdx));
     189          902 :         if (!HeapTupleIsValid(tup))
     190            0 :             elog(ERROR, "cache lookup failed for relation %u", partIdx);
     191          902 :         classForm = (Form_pg_class) GETSTRUCT(tup);
     192          902 :         ispartition = classForm->relispartition;
     193          902 :         ReleaseSysCache(tup);
     194          902 :         if (!ispartition)
     195          266 :             continue;
     196          636 :         if (get_partition_parent(partIdx, false) == indexId)
     197              :         {
     198          621 :             list_free(idxlist);
     199          621 :             return partIdx;
     200              :         }
     201              :     }
     202              : 
     203          177 :     list_free(idxlist);
     204          177 :     return InvalidOid;
     205              : }
     206              : 
     207              : /*
     208              :  * map_partition_varattnos - maps varattnos of all Vars in 'expr' (that have
     209              :  * varno 'fromrel_varno') from the attnums of 'from_rel' to the attnums of
     210              :  * 'to_rel', each of which may be either a leaf partition or a partitioned
     211              :  * table, but both of which must be from the same partitioning hierarchy.
     212              :  *
     213              :  * We need this because even though all of the same column names must be
     214              :  * present in all relations in the hierarchy, and they must also have the
     215              :  * same types, the attnums may be different.
     216              :  *
     217              :  * Note: this will work on any node tree, so really the argument and result
     218              :  * should be declared "Node *".  But a substantial majority of the callers
     219              :  * are working on Lists, so it's less messy to do the casts internally.
     220              :  */
     221              : List *
     222         4493 : map_partition_varattnos(List *expr, int fromrel_varno,
     223              :                         Relation to_rel, Relation from_rel)
     224              : {
     225         4493 :     if (expr != NIL)
     226              :     {
     227              :         AttrMap    *part_attmap;
     228              :         bool        found_whole_row;
     229              : 
     230         3780 :         part_attmap = build_attrmap_by_name(RelationGetDescr(to_rel),
     231              :                                             RelationGetDescr(from_rel),
     232              :                                             false);
     233         3780 :         expr = (List *) map_variable_attnos((Node *) expr,
     234              :                                             fromrel_varno, 0,
     235              :                                             part_attmap,
     236         3780 :                                             RelationGetForm(to_rel)->reltype,
     237              :                                             &found_whole_row);
     238              :         /* Since we provided a to_rowtype, we may ignore found_whole_row. */
     239              :     }
     240              : 
     241         4493 :     return expr;
     242              : }
     243              : 
     244              : /*
     245              :  * Checks if any of the 'attnums' is a partition key attribute for rel
     246              :  *
     247              :  * Sets *used_in_expr if any of the 'attnums' is found to be referenced in some
     248              :  * partition key expression.  It's possible for a column to be both used
     249              :  * directly and as part of an expression; if that happens, *used_in_expr may
     250              :  * end up as either true or false.  That's OK for current uses of this
     251              :  * function, because *used_in_expr is only used to tailor the error message
     252              :  * text.
     253              :  */
     254              : bool
     255         1766 : has_partition_attrs(Relation rel, Bitmapset *attnums, bool *used_in_expr)
     256              : {
     257              :     PartitionKey key;
     258              :     int         partnatts;
     259              :     List       *partexprs;
     260              :     ListCell   *partexprs_item;
     261              :     int         i;
     262              : 
     263         1766 :     if (attnums == NULL || rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
     264         1509 :         return false;
     265              : 
     266          257 :     key = RelationGetPartitionKey(rel);
     267          257 :     partnatts = get_partition_natts(key);
     268          257 :     partexprs = get_partition_exprs(key);
     269              : 
     270          257 :     partexprs_item = list_head(partexprs);
     271          515 :     for (i = 0; i < partnatts; i++)
     272              :     {
     273          282 :         AttrNumber  partattno = get_partition_col_attnum(key, i);
     274              : 
     275          282 :         if (partattno != 0)
     276              :         {
     277          245 :             if (bms_is_member(partattno - FirstLowInvalidHeapAttributeNumber,
     278              :                               attnums))
     279              :             {
     280           15 :                 if (used_in_expr)
     281           15 :                     *used_in_expr = false;
     282           15 :                 return true;
     283              :             }
     284              :         }
     285              :         else
     286              :         {
     287              :             /* Arbitrary expression */
     288           37 :             Node       *expr = (Node *) lfirst(partexprs_item);
     289           37 :             Bitmapset  *expr_attrs = NULL;
     290              : 
     291              :             /* Find all attributes referenced */
     292           37 :             pull_varattnos(expr, 1, &expr_attrs);
     293           37 :             partexprs_item = lnext(partexprs, partexprs_item);
     294              : 
     295           37 :             if (bms_overlap(attnums, expr_attrs))
     296              :             {
     297            9 :                 if (used_in_expr)
     298            9 :                     *used_in_expr = true;
     299            9 :                 return true;
     300              :             }
     301              :         }
     302              :     }
     303              : 
     304          233 :     return false;
     305              : }
     306              : 
     307              : /*
     308              :  * get_default_partition_oid
     309              :  *
     310              :  * Given a relation OID, return the OID of the default partition, if one
     311              :  * exists.  Use get_default_oid_from_partdesc where possible, for
     312              :  * efficiency.
     313              :  */
     314              : Oid
     315         5339 : get_default_partition_oid(Oid parentId)
     316              : {
     317              :     HeapTuple   tuple;
     318         5339 :     Oid         defaultPartId = InvalidOid;
     319              : 
     320         5339 :     tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
     321              : 
     322         5339 :     if (HeapTupleIsValid(tuple))
     323              :     {
     324              :         Form_pg_partitioned_table part_table_form;
     325              : 
     326         5339 :         part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
     327         5339 :         defaultPartId = part_table_form->partdefid;
     328         5339 :         ReleaseSysCache(tuple);
     329              :     }
     330              : 
     331         5339 :     return defaultPartId;
     332              : }
     333              : 
     334              : /*
     335              :  * update_default_partition_oid
     336              :  *
     337              :  * Update pg_partitioned_table.partdefid with a new default partition OID.
     338              :  */
     339              : void
     340          681 : update_default_partition_oid(Oid parentId, Oid defaultPartId)
     341              : {
     342              :     HeapTuple   tuple;
     343              :     Relation    pg_partitioned_table;
     344              :     Form_pg_partitioned_table part_table_form;
     345              : 
     346          681 :     pg_partitioned_table = table_open(PartitionedRelationId, RowExclusiveLock);
     347              : 
     348          681 :     tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
     349              : 
     350          681 :     if (!HeapTupleIsValid(tuple))
     351            0 :         elog(ERROR, "cache lookup failed for partition key of relation %u",
     352              :              parentId);
     353              : 
     354          681 :     part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
     355          681 :     part_table_form->partdefid = defaultPartId;
     356          681 :     CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
     357              : 
     358          681 :     heap_freetuple(tuple);
     359          681 :     table_close(pg_partitioned_table, RowExclusiveLock);
     360          681 : }
     361              : 
     362              : /*
     363              :  * get_proposed_default_constraint
     364              :  *
     365              :  * This function returns the negation of new_part_constraints, which
     366              :  * would be an integral part of the default partition constraints after
     367              :  * addition of the partition to which the new_part_constraints belongs.
     368              :  */
     369              : List *
     370          247 : get_proposed_default_constraint(List *new_part_constraints)
     371              : {
     372              :     Expr       *defPartConstraint;
     373              : 
     374          247 :     defPartConstraint = make_ands_explicit(new_part_constraints);
     375              : 
     376              :     /*
     377              :      * Derive the partition constraints of default partition by negating the
     378              :      * given partition constraints. The partition constraint never evaluates
     379              :      * to NULL, so negating it like this is safe.
     380              :      */
     381          247 :     defPartConstraint = makeBoolExpr(NOT_EXPR,
     382          247 :                                      list_make1(defPartConstraint),
     383              :                                      -1);
     384              : 
     385              :     /* Simplify, to put the negated expression into canonical form */
     386              :     defPartConstraint =
     387          247 :         (Expr *) eval_const_expressions(NULL,
     388              :                                         (Node *) defPartConstraint);
     389          247 :     defPartConstraint = canonicalize_qual(defPartConstraint, true);
     390              : 
     391          247 :     return make_ands_implicit(defPartConstraint);
     392              : }
        

Generated by: LCOV version 2.0-1