LCOV - code coverage report
Current view: top level - src/backend/catalog - pg_inherits.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 151 161 93.8 %
Date: 2024-03-29 05:11:05 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pg_inherits.c
       4             :  *    routines to support manipulation of the pg_inherits relation
       5             :  *
       6             :  * Note: currently, this module mostly contains inquiry functions; actual
       7             :  * creation and deletion of pg_inherits entries is mostly done in tablecmds.c.
       8             :  * Perhaps someday that code should be moved here, but it'd have to be
       9             :  * disentangled from other stuff such as pg_depend updates.
      10             :  *
      11             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
      12             :  * Portions Copyright (c) 1994, Regents of the University of California
      13             :  *
      14             :  *
      15             :  * IDENTIFICATION
      16             :  *    src/backend/catalog/pg_inherits.c
      17             :  *
      18             :  *-------------------------------------------------------------------------
      19             :  */
      20             : #include "postgres.h"
      21             : 
      22             : #include "access/genam.h"
      23             : #include "access/htup_details.h"
      24             : #include "access/table.h"
      25             : #include "catalog/indexing.h"
      26             : #include "catalog/pg_inherits.h"
      27             : #include "parser/parse_type.h"
      28             : #include "storage/lmgr.h"
      29             : #include "utils/builtins.h"
      30             : #include "utils/fmgroids.h"
      31             : #include "utils/snapmgr.h"
      32             : #include "utils/syscache.h"
      33             : 
      34             : /*
      35             :  * Entry of a hash table used in find_all_inheritors. See below.
      36             :  */
      37             : typedef struct SeenRelsEntry
      38             : {
      39             :     Oid         rel_id;         /* relation oid */
      40             :     int         list_index;     /* its position in output list(s) */
      41             : } SeenRelsEntry;
      42             : 
      43             : /*
      44             :  * find_inheritance_children
      45             :  *
      46             :  * Returns a list containing the OIDs of all relations which
      47             :  * inherit *directly* from the relation with OID 'parentrelId'.
      48             :  *
      49             :  * The specified lock type is acquired on each child relation (but not on the
      50             :  * given rel; caller should already have locked it).  If lockmode is NoLock
      51             :  * then no locks are acquired, but caller must beware of race conditions
      52             :  * against possible DROPs of child relations.
      53             :  *
      54             :  * Partitions marked as being detached are omitted; see
      55             :  * find_inheritance_children_extended for details.
      56             :  */
      57             : List *
      58       53122 : find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
      59             : {
      60       53122 :     return find_inheritance_children_extended(parentrelId, true, lockmode,
      61             :                                               NULL, NULL);
      62             : }
      63             : 
      64             : /*
      65             :  * find_inheritance_children_extended
      66             :  *
      67             :  * As find_inheritance_children, with more options regarding detached
      68             :  * partitions.
      69             :  *
      70             :  * If a partition's pg_inherits row is marked "detach pending",
      71             :  * *detached_exist (if not null) is set true.
      72             :  *
      73             :  * If omit_detached is true and there is an active snapshot (not the same as
      74             :  * the catalog snapshot used to scan pg_inherits!) and a pg_inherits tuple
      75             :  * marked "detach pending" is visible to that snapshot, then that partition is
      76             :  * omitted from the output list.  This makes partitions invisible depending on
      77             :  * whether the transaction that marked those partitions as detached appears
      78             :  * committed to the active snapshot.  In addition, *detached_xmin (if not null)
      79             :  * is set to the xmin of the row of the detached partition.
      80             :  */
      81             : List *
      82       73676 : find_inheritance_children_extended(Oid parentrelId, bool omit_detached,
      83             :                                    LOCKMODE lockmode, bool *detached_exist,
      84             :                                    TransactionId *detached_xmin)
      85             : {
      86       73676 :     List       *list = NIL;
      87             :     Relation    relation;
      88             :     SysScanDesc scan;
      89             :     ScanKeyData key[1];
      90             :     HeapTuple   inheritsTuple;
      91             :     Oid         inhrelid;
      92             :     Oid        *oidarr;
      93             :     int         maxoids,
      94             :                 numoids,
      95             :                 i;
      96             : 
      97             :     /*
      98             :      * Can skip the scan if pg_class shows the relation has never had a
      99             :      * subclass.
     100             :      */
     101       73676 :     if (!has_subclass(parentrelId))
     102       45154 :         return NIL;
     103             : 
     104             :     /*
     105             :      * Scan pg_inherits and build a working array of subclass OIDs.
     106             :      */
     107       28522 :     maxoids = 32;
     108       28522 :     oidarr = (Oid *) palloc(maxoids * sizeof(Oid));
     109       28522 :     numoids = 0;
     110             : 
     111       28522 :     relation = table_open(InheritsRelationId, AccessShareLock);
     112             : 
     113       28522 :     ScanKeyInit(&key[0],
     114             :                 Anum_pg_inherits_inhparent,
     115             :                 BTEqualStrategyNumber, F_OIDEQ,
     116             :                 ObjectIdGetDatum(parentrelId));
     117             : 
     118       28522 :     scan = systable_beginscan(relation, InheritsParentIndexId, true,
     119             :                               NULL, 1, key);
     120             : 
     121       83322 :     while ((inheritsTuple = systable_getnext(scan)) != NULL)
     122             :     {
     123             :         /*
     124             :          * Cope with partitions concurrently being detached.  When we see a
     125             :          * partition marked "detach pending", we omit it from the returned set
     126             :          * of visible partitions if caller requested that and the tuple's xmin
     127             :          * does not appear in progress to the active snapshot.  (If there's no
     128             :          * active snapshot set, that means we're not running a user query, so
     129             :          * it's OK to always include detached partitions in that case; if the
     130             :          * xmin is still running to the active snapshot, then the partition
     131             :          * has not been detached yet and so we include it.)
     132             :          *
     133             :          * The reason for this hack is that we want to avoid seeing the
     134             :          * partition as alive in RI queries during REPEATABLE READ or
     135             :          * SERIALIZABLE transactions: such queries use a different snapshot
     136             :          * than the one used by regular (user) queries.
     137             :          */
     138       54800 :         if (((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhdetachpending)
     139             :         {
     140         116 :             if (detached_exist)
     141         112 :                 *detached_exist = true;
     142             : 
     143         116 :             if (omit_detached && ActiveSnapshotSet())
     144             :             {
     145             :                 TransactionId xmin;
     146             :                 Snapshot    snap;
     147             : 
     148          94 :                 xmin = HeapTupleHeaderGetXmin(inheritsTuple->t_data);
     149          94 :                 snap = GetActiveSnapshot();
     150             : 
     151          94 :                 if (!XidInMVCCSnapshot(xmin, snap))
     152             :                 {
     153          70 :                     if (detached_xmin)
     154             :                     {
     155             :                         /*
     156             :                          * Two detached partitions should not occur (see
     157             :                          * checks in MarkInheritDetached), but if they do,
     158             :                          * track the newer of the two.  Make sure to warn the
     159             :                          * user, so that they can clean up.  Since this is
     160             :                          * just a cross-check against potentially corrupt
     161             :                          * catalogs, we don't make it a full-fledged error
     162             :                          * message.
     163             :                          */
     164          66 :                         if (*detached_xmin != InvalidTransactionId)
     165             :                         {
     166           0 :                             elog(WARNING, "more than one partition pending detach found for table with OID %u",
     167             :                                  parentrelId);
     168           0 :                             if (TransactionIdFollows(xmin, *detached_xmin))
     169           0 :                                 *detached_xmin = xmin;
     170             :                         }
     171             :                         else
     172          66 :                             *detached_xmin = xmin;
     173             :                     }
     174             : 
     175             :                     /* Don't add the partition to the output list */
     176          70 :                     continue;
     177             :                 }
     178             :             }
     179             :         }
     180             : 
     181       54730 :         inhrelid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhrelid;
     182       54730 :         if (numoids >= maxoids)
     183             :         {
     184           0 :             maxoids *= 2;
     185           0 :             oidarr = (Oid *) repalloc(oidarr, maxoids * sizeof(Oid));
     186             :         }
     187       54730 :         oidarr[numoids++] = inhrelid;
     188             :     }
     189             : 
     190       28522 :     systable_endscan(scan);
     191             : 
     192       28522 :     table_close(relation, AccessShareLock);
     193             : 
     194             :     /*
     195             :      * If we found more than one child, sort them by OID.  This ensures
     196             :      * reasonably consistent behavior regardless of the vagaries of an
     197             :      * indexscan.  This is important since we need to be sure all backends
     198             :      * lock children in the same order to avoid needless deadlocks.
     199             :      */
     200       28522 :     if (numoids > 1)
     201       16296 :         qsort(oidarr, numoids, sizeof(Oid), oid_cmp);
     202             : 
     203             :     /*
     204             :      * Acquire locks and build the result list.
     205             :      */
     206       83252 :     for (i = 0; i < numoids; i++)
     207             :     {
     208       54730 :         inhrelid = oidarr[i];
     209             : 
     210       54730 :         if (lockmode != NoLock)
     211             :         {
     212             :             /* Get the lock to synchronize against concurrent drop */
     213       15902 :             LockRelationOid(inhrelid, lockmode);
     214             : 
     215             :             /*
     216             :              * Now that we have the lock, double-check to see if the relation
     217             :              * really exists or not.  If not, assume it was dropped while we
     218             :              * waited to acquire lock, and ignore it.
     219             :              */
     220       15902 :             if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(inhrelid)))
     221             :             {
     222             :                 /* Release useless lock */
     223           2 :                 UnlockRelationOid(inhrelid, lockmode);
     224             :                 /* And ignore this relation */
     225           2 :                 continue;
     226             :             }
     227             :         }
     228             : 
     229       54728 :         list = lappend_oid(list, inhrelid);
     230             :     }
     231             : 
     232       28522 :     pfree(oidarr);
     233             : 
     234       28522 :     return list;
     235             : }
     236             : 
     237             : 
     238             : /*
     239             :  * find_all_inheritors -
     240             :  *      Returns a list of relation OIDs including the given rel plus
     241             :  *      all relations that inherit from it, directly or indirectly.
     242             :  *      Optionally, it also returns the number of parents found for
     243             :  *      each such relation within the inheritance tree rooted at the
     244             :  *      given rel.
     245             :  *
     246             :  * The specified lock type is acquired on all child relations (but not on the
     247             :  * given rel; caller should already have locked it).  If lockmode is NoLock
     248             :  * then no locks are acquired, but caller must beware of race conditions
     249             :  * against possible DROPs of child relations.
     250             :  *
     251             :  * NB - No current callers of this routine are interested in children being
     252             :  * concurrently detached, so there's no provision to include them.
     253             :  */
     254             : List *
     255       23848 : find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
     256             : {
     257             :     /* hash table for O(1) rel_oid -> rel_numparents cell lookup */
     258             :     HTAB       *seen_rels;
     259             :     HASHCTL     ctl;
     260             :     List       *rels_list,
     261             :                *rel_numparents;
     262             :     ListCell   *l;
     263             : 
     264       23848 :     ctl.keysize = sizeof(Oid);
     265       23848 :     ctl.entrysize = sizeof(SeenRelsEntry);
     266       23848 :     ctl.hcxt = CurrentMemoryContext;
     267             : 
     268       23848 :     seen_rels = hash_create("find_all_inheritors temporary table",
     269             :                             32, /* start small and extend */
     270             :                             &ctl,
     271             :                             HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
     272             : 
     273             :     /*
     274             :      * We build a list starting with the given rel and adding all direct and
     275             :      * indirect children.  We can use a single list as both the record of
     276             :      * already-found rels and the agenda of rels yet to be scanned for more
     277             :      * children.  This is a bit tricky but works because the foreach() macro
     278             :      * doesn't fetch the next list element until the bottom of the loop.  Note
     279             :      * that we can't keep pointers into the output lists; but an index is
     280             :      * sufficient.
     281             :      */
     282       23848 :     rels_list = list_make1_oid(parentrelId);
     283       23848 :     rel_numparents = list_make1_int(0);
     284             : 
     285       68942 :     foreach(l, rels_list)
     286             :     {
     287       45094 :         Oid         currentrel = lfirst_oid(l);
     288             :         List       *currentchildren;
     289             :         ListCell   *lc;
     290             : 
     291             :         /* Get the direct children of this rel */
     292       45094 :         currentchildren = find_inheritance_children(currentrel, lockmode);
     293             : 
     294             :         /*
     295             :          * Add to the queue only those children not already seen. This avoids
     296             :          * making duplicate entries in case of multiple inheritance paths from
     297             :          * the same parent.  (It'll also keep us from getting into an infinite
     298             :          * loop, though theoretically there can't be any cycles in the
     299             :          * inheritance graph anyway.)
     300             :          */
     301       66820 :         foreach(lc, currentchildren)
     302             :         {
     303       21726 :             Oid         child_oid = lfirst_oid(lc);
     304             :             bool        found;
     305             :             SeenRelsEntry *hash_entry;
     306             : 
     307       21726 :             hash_entry = hash_search(seen_rels, &child_oid, HASH_ENTER, &found);
     308       21726 :             if (found)
     309             :             {
     310             :                 /* if the rel is already there, bump number-of-parents counter */
     311             :                 ListCell   *numparents_cell;
     312             : 
     313         480 :                 numparents_cell = list_nth_cell(rel_numparents,
     314             :                                                 hash_entry->list_index);
     315         480 :                 lfirst_int(numparents_cell)++;
     316             :             }
     317             :             else
     318             :             {
     319             :                 /* if it's not there, add it. expect 1 parent, initially. */
     320       21246 :                 hash_entry->list_index = list_length(rels_list);
     321       21246 :                 rels_list = lappend_oid(rels_list, child_oid);
     322       21246 :                 rel_numparents = lappend_int(rel_numparents, 1);
     323             :             }
     324             :         }
     325             :     }
     326             : 
     327       23848 :     if (numparents)
     328        1034 :         *numparents = rel_numparents;
     329             :     else
     330       22814 :         list_free(rel_numparents);
     331             : 
     332       23848 :     hash_destroy(seen_rels);
     333             : 
     334       23848 :     return rels_list;
     335             : }
     336             : 
     337             : 
     338             : /*
     339             :  * has_subclass - does this relation have any children?
     340             :  *
     341             :  * In the current implementation, has_subclass returns whether a
     342             :  * particular class *might* have a subclass. It will not return the
     343             :  * correct result if a class had a subclass which was later dropped.
     344             :  * This is because relhassubclass in pg_class is not updated immediately
     345             :  * when a subclass is dropped, primarily because of concurrency concerns.
     346             :  *
     347             :  * Currently has_subclass is only used as an efficiency hack to skip
     348             :  * unnecessary inheritance searches, so this is OK.  Note that ANALYZE
     349             :  * on a childless table will clean up the obsolete relhassubclass flag.
     350             :  *
     351             :  * Although this doesn't actually touch pg_inherits, it seems reasonable
     352             :  * to keep it here since it's normally used with the other routines here.
     353             :  */
     354             : bool
     355      404946 : has_subclass(Oid relationId)
     356             : {
     357             :     HeapTuple   tuple;
     358             :     bool        result;
     359             : 
     360      404946 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
     361      404946 :     if (!HeapTupleIsValid(tuple))
     362           0 :         elog(ERROR, "cache lookup failed for relation %u", relationId);
     363             : 
     364      404946 :     result = ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass;
     365      404946 :     ReleaseSysCache(tuple);
     366      404946 :     return result;
     367             : }
     368             : 
     369             : /*
     370             :  * has_superclass - does this relation inherit from another?
     371             :  *
     372             :  * Unlike has_subclass, this can be relied on to give an accurate answer.
     373             :  * However, the caller must hold a lock on the given relation so that it
     374             :  * can't be concurrently added to or removed from an inheritance hierarchy.
     375             :  */
     376             : bool
     377         554 : has_superclass(Oid relationId)
     378             : {
     379             :     Relation    catalog;
     380             :     SysScanDesc scan;
     381             :     ScanKeyData skey;
     382             :     bool        result;
     383             : 
     384         554 :     catalog = table_open(InheritsRelationId, AccessShareLock);
     385         554 :     ScanKeyInit(&skey, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber,
     386             :                 F_OIDEQ, ObjectIdGetDatum(relationId));
     387         554 :     scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
     388             :                               NULL, 1, &skey);
     389         554 :     result = HeapTupleIsValid(systable_getnext(scan));
     390         554 :     systable_endscan(scan);
     391         554 :     table_close(catalog, AccessShareLock);
     392             : 
     393         554 :     return result;
     394             : }
     395             : 
     396             : /*
     397             :  * Given two type OIDs, determine whether the first is a complex type
     398             :  * (class type) that inherits from the second.
     399             :  *
     400             :  * This essentially asks whether the first type is guaranteed to be coercible
     401             :  * to the second.  Therefore, we allow the first type to be a domain over a
     402             :  * complex type that inherits from the second; that creates no difficulties.
     403             :  * But the second type cannot be a domain.
     404             :  */
     405             : bool
     406      669736 : typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
     407             : {
     408      669736 :     bool        result = false;
     409             :     Oid         subclassRelid;
     410             :     Oid         superclassRelid;
     411             :     Relation    inhrel;
     412             :     List       *visited,
     413             :                *queue;
     414             :     ListCell   *queue_item;
     415             : 
     416             :     /* We need to work with the associated relation OIDs */
     417      669736 :     subclassRelid = typeOrDomainTypeRelid(subclassTypeId);
     418      669736 :     if (subclassRelid == InvalidOid)
     419      657450 :         return false;           /* not a complex type or domain over one */
     420       12286 :     superclassRelid = typeidTypeRelid(superclassTypeId);
     421       12286 :     if (superclassRelid == InvalidOid)
     422       12166 :         return false;           /* not a complex type */
     423             : 
     424             :     /* No point in searching if the superclass has no subclasses */
     425         120 :     if (!has_subclass(superclassRelid))
     426          12 :         return false;
     427             : 
     428             :     /*
     429             :      * Begin the search at the relation itself, so add its relid to the queue.
     430             :      */
     431         108 :     queue = list_make1_oid(subclassRelid);
     432         108 :     visited = NIL;
     433             : 
     434         108 :     inhrel = table_open(InheritsRelationId, AccessShareLock);
     435             : 
     436             :     /*
     437             :      * Use queue to do a breadth-first traversal of the inheritance graph from
     438             :      * the relid supplied up to the root.  Notice that we append to the queue
     439             :      * inside the loop --- this is okay because the foreach() macro doesn't
     440             :      * advance queue_item until the next loop iteration begins.
     441             :      */
     442         108 :     foreach(queue_item, queue)
     443             :     {
     444         108 :         Oid         this_relid = lfirst_oid(queue_item);
     445             :         ScanKeyData skey;
     446             :         SysScanDesc inhscan;
     447             :         HeapTuple   inhtup;
     448             : 
     449             :         /*
     450             :          * If we've seen this relid already, skip it.  This avoids extra work
     451             :          * in multiple-inheritance scenarios, and also protects us from an
     452             :          * infinite loop in case there is a cycle in pg_inherits (though
     453             :          * theoretically that shouldn't happen).
     454             :          */
     455         108 :         if (list_member_oid(visited, this_relid))
     456           0 :             continue;
     457             : 
     458             :         /*
     459             :          * Okay, this is a not-yet-seen relid. Add it to the list of
     460             :          * already-visited OIDs, then find all the types this relid inherits
     461             :          * from and add them to the queue.
     462             :          */
     463         108 :         visited = lappend_oid(visited, this_relid);
     464             : 
     465         108 :         ScanKeyInit(&skey,
     466             :                     Anum_pg_inherits_inhrelid,
     467             :                     BTEqualStrategyNumber, F_OIDEQ,
     468             :                     ObjectIdGetDatum(this_relid));
     469             : 
     470         108 :         inhscan = systable_beginscan(inhrel, InheritsRelidSeqnoIndexId, true,
     471             :                                      NULL, 1, &skey);
     472             : 
     473         120 :         while ((inhtup = systable_getnext(inhscan)) != NULL)
     474             :         {
     475         120 :             Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inhtup);
     476         120 :             Oid         inhparent = inh->inhparent;
     477             : 
     478             :             /* If this is the target superclass, we're done */
     479         120 :             if (inhparent == superclassRelid)
     480             :             {
     481         108 :                 result = true;
     482         108 :                 break;
     483             :             }
     484             : 
     485             :             /* Else add to queue */
     486          12 :             queue = lappend_oid(queue, inhparent);
     487             :         }
     488             : 
     489         108 :         systable_endscan(inhscan);
     490             : 
     491         108 :         if (result)
     492         108 :             break;
     493             :     }
     494             : 
     495             :     /* clean up ... */
     496         108 :     table_close(inhrel, AccessShareLock);
     497             : 
     498         108 :     list_free(visited);
     499         108 :     list_free(queue);
     500             : 
     501         108 :     return result;
     502             : }
     503             : 
     504             : /*
     505             :  * Create a single pg_inherits row with the given data
     506             :  */
     507             : void
     508       14040 : StoreSingleInheritance(Oid relationId, Oid parentOid, int32 seqNumber)
     509             : {
     510             :     Datum       values[Natts_pg_inherits];
     511             :     bool        nulls[Natts_pg_inherits];
     512             :     HeapTuple   tuple;
     513             :     Relation    inhRelation;
     514             : 
     515       14040 :     inhRelation = table_open(InheritsRelationId, RowExclusiveLock);
     516             : 
     517             :     /*
     518             :      * Make the pg_inherits entry
     519             :      */
     520       14040 :     values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId);
     521       14040 :     values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid);
     522       14040 :     values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(seqNumber);
     523       14040 :     values[Anum_pg_inherits_inhdetachpending - 1] = BoolGetDatum(false);
     524             : 
     525       14040 :     memset(nulls, 0, sizeof(nulls));
     526             : 
     527       14040 :     tuple = heap_form_tuple(RelationGetDescr(inhRelation), values, nulls);
     528             : 
     529       14040 :     CatalogTupleInsert(inhRelation, tuple);
     530             : 
     531       14040 :     heap_freetuple(tuple);
     532             : 
     533       14040 :     table_close(inhRelation, RowExclusiveLock);
     534       14040 : }
     535             : 
     536             : /*
     537             :  * DeleteInheritsTuple
     538             :  *
     539             :  * Delete pg_inherits tuples with the given inhrelid.  inhparent may be given
     540             :  * as InvalidOid, in which case all tuples matching inhrelid are deleted;
     541             :  * otherwise only delete tuples with the specified inhparent.
     542             :  *
     543             :  * expect_detach_pending is the expected state of the inhdetachpending flag.
     544             :  * If the catalog row does not match that state, an error is raised.
     545             :  *
     546             :  * childname is the partition name, if a table; pass NULL for regular
     547             :  * inheritance or when working with other relation kinds.
     548             :  *
     549             :  * Returns whether at least one row was deleted.
     550             :  */
     551             : bool
     552       22496 : DeleteInheritsTuple(Oid inhrelid, Oid inhparent, bool expect_detach_pending,
     553             :                     const char *childname)
     554             : {
     555       22496 :     bool        found = false;
     556             :     Relation    catalogRelation;
     557             :     ScanKeyData key;
     558             :     SysScanDesc scan;
     559             :     HeapTuple   inheritsTuple;
     560             : 
     561             :     /*
     562             :      * Find pg_inherits entries by inhrelid.
     563             :      */
     564       22496 :     catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
     565       22496 :     ScanKeyInit(&key,
     566             :                 Anum_pg_inherits_inhrelid,
     567             :                 BTEqualStrategyNumber, F_OIDEQ,
     568             :                 ObjectIdGetDatum(inhrelid));
     569       22496 :     scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
     570             :                               true, NULL, 1, &key);
     571             : 
     572       25154 :     while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
     573             :     {
     574             :         Oid         parent;
     575             : 
     576             :         /* Compare inhparent if it was given, and do the actual deletion. */
     577        2658 :         parent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
     578        2658 :         if (!OidIsValid(inhparent) || parent == inhparent)
     579             :         {
     580             :             bool        detach_pending;
     581             : 
     582        2634 :             detach_pending =
     583        2634 :                 ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhdetachpending;
     584             : 
     585             :             /*
     586             :              * Raise error depending on state.  This should only happen for
     587             :              * partitions, but we have no way to cross-check.
     588             :              */
     589        2634 :             if (detach_pending && !expect_detach_pending)
     590           0 :                 ereport(ERROR,
     591             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     592             :                          errmsg("cannot detach partition \"%s\"",
     593             :                                 childname ? childname : "unknown relation"),
     594             :                          errdetail("The partition is being detached concurrently or has an unfinished detach."),
     595             :                          errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation.")));
     596        2634 :             if (!detach_pending && expect_detach_pending)
     597           0 :                 ereport(ERROR,
     598             :                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     599             :                          errmsg("cannot complete detaching partition \"%s\"",
     600             :                                 childname ? childname : "unknown relation"),
     601             :                          errdetail("There's no pending concurrent detach.")));
     602             : 
     603        2634 :             CatalogTupleDelete(catalogRelation, &inheritsTuple->t_self);
     604        2634 :             found = true;
     605             :         }
     606             :     }
     607             : 
     608             :     /* Done */
     609       22496 :     systable_endscan(scan);
     610       22496 :     table_close(catalogRelation, RowExclusiveLock);
     611             : 
     612       22496 :     return found;
     613             : }
     614             : 
     615             : /*
     616             :  * Return whether the pg_inherits tuple for a partition has the "detach
     617             :  * pending" flag set.
     618             :  */
     619             : bool
     620        1278 : PartitionHasPendingDetach(Oid partoid)
     621             : {
     622             :     Relation    catalogRelation;
     623             :     ScanKeyData key;
     624             :     SysScanDesc scan;
     625             :     HeapTuple   inheritsTuple;
     626             : 
     627             :     /* We don't have a good way to verify it is in fact a partition */
     628             : 
     629             :     /*
     630             :      * Find the pg_inherits entry by inhrelid.  (There should only be one.)
     631             :      */
     632        1278 :     catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
     633        1278 :     ScanKeyInit(&key,
     634             :                 Anum_pg_inherits_inhrelid,
     635             :                 BTEqualStrategyNumber, F_OIDEQ,
     636             :                 ObjectIdGetDatum(partoid));
     637        1278 :     scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
     638             :                               true, NULL, 1, &key);
     639             : 
     640        1278 :     while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
     641             :     {
     642             :         bool        detached;
     643             : 
     644        1278 :         detached =
     645        1278 :             ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhdetachpending;
     646             : 
     647             :         /* Done */
     648        1278 :         systable_endscan(scan);
     649        1278 :         table_close(catalogRelation, RowExclusiveLock);
     650             : 
     651        1278 :         return detached;
     652             :     }
     653             : 
     654           0 :     elog(ERROR, "relation %u is not a partition", partoid);
     655             :     return false;               /* keep compiler quiet */
     656             : }

Generated by: LCOV version 1.14