LCOV - code coverage report
Current view: top level - src/backend/executor - nodeLockRows.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 83.8 % 130 109
Test Date: 2026-03-09 07:14:48 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * nodeLockRows.c
       4              :  *    Routines to handle FOR UPDATE/FOR SHARE row locking
       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/executor/nodeLockRows.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : /*
      16              :  * INTERFACE ROUTINES
      17              :  *      ExecLockRows        - fetch locked rows
      18              :  *      ExecInitLockRows    - initialize node and subnodes..
      19              :  *      ExecEndLockRows     - shutdown node and subnodes
      20              :  */
      21              : 
      22              : #include "postgres.h"
      23              : 
      24              : #include "access/tableam.h"
      25              : #include "access/xact.h"
      26              : #include "executor/executor.h"
      27              : #include "executor/nodeLockRows.h"
      28              : #include "foreign/fdwapi.h"
      29              : #include "miscadmin.h"
      30              : #include "utils/rel.h"
      31              : 
      32              : 
      33              : /* ----------------------------------------------------------------
      34              :  *      ExecLockRows
      35              :  * ----------------------------------------------------------------
      36              :  */
      37              : static TupleTableSlot *         /* return: a tuple or NULL */
      38        86104 : ExecLockRows(PlanState *pstate)
      39              : {
      40        86104 :     LockRowsState *node = castNode(LockRowsState, pstate);
      41              :     TupleTableSlot *slot;
      42              :     EState     *estate;
      43              :     PlanState  *outerPlan;
      44              :     bool        epq_needed;
      45              :     ListCell   *lc;
      46              : 
      47        86104 :     CHECK_FOR_INTERRUPTS();
      48              : 
      49              :     /*
      50              :      * get information from the node
      51              :      */
      52        86104 :     estate = node->ps.state;
      53        86104 :     outerPlan = outerPlanState(node);
      54              : 
      55              :     /*
      56              :      * Get next tuple from subplan, if any.
      57              :      */
      58           57 : lnext:
      59        86161 :     slot = ExecProcNode(outerPlan);
      60              : 
      61        86161 :     if (TupIsNull(slot))
      62              :     {
      63              :         /* Release any resources held by EPQ mechanism before exiting */
      64         4466 :         EvalPlanQualEnd(&node->lr_epqstate);
      65         4466 :         return NULL;
      66              :     }
      67              : 
      68              :     /* We don't need EvalPlanQual unless we get updated tuple version(s) */
      69        81695 :     epq_needed = false;
      70              : 
      71              :     /*
      72              :      * Attempt to lock the source tuple(s).  (Note we only have locking
      73              :      * rowmarks in lr_arowMarks.)
      74              :      */
      75       163510 :     foreach(lc, node->lr_arowMarks)
      76              :     {
      77        81902 :         ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
      78        81902 :         ExecRowMark *erm = aerm->rowmark;
      79              :         Datum       datum;
      80              :         bool        isNull;
      81              :         ItemPointerData tid;
      82              :         TM_FailureData tmfd;
      83              :         LockTupleMode lockmode;
      84        81902 :         int         lockflags = 0;
      85              :         TM_Result   test;
      86              :         TupleTableSlot *markSlot;
      87              : 
      88              :         /* clear any leftover test tuple for this rel */
      89        81902 :         markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
      90        81902 :         ExecClearTuple(markSlot);
      91              : 
      92              :         /* if child rel, must check whether it produced this row */
      93        81902 :         if (erm->rti != erm->prti)
      94              :         {
      95              :             Oid         tableoid;
      96              : 
      97          781 :             datum = ExecGetJunkAttribute(slot,
      98          781 :                                          aerm->toidAttNo,
      99              :                                          &isNull);
     100              :             /* shouldn't ever get a null result... */
     101          781 :             if (isNull)
     102            0 :                 elog(ERROR, "tableoid is NULL");
     103          781 :             tableoid = DatumGetObjectId(datum);
     104              : 
     105              :             Assert(OidIsValid(erm->relid));
     106          781 :             if (tableoid != erm->relid)
     107              :             {
     108              :                 /* this child is inactive right now */
     109          158 :                 erm->ermActive = false;
     110          158 :                 ItemPointerSetInvalid(&(erm->curCtid));
     111          158 :                 continue;
     112              :             }
     113              :         }
     114        81744 :         erm->ermActive = true;
     115              : 
     116              :         /* fetch the tuple's ctid */
     117        81744 :         datum = ExecGetJunkAttribute(slot,
     118        81744 :                                      aerm->ctidAttNo,
     119              :                                      &isNull);
     120              :         /* shouldn't ever get a null result... */
     121        81744 :         if (isNull)
     122            0 :             elog(ERROR, "ctid is NULL");
     123              : 
     124              :         /* requests for foreign tables must be passed to their FDW */
     125        81744 :         if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     126            0 :         {
     127              :             FdwRoutine *fdwroutine;
     128            0 :             bool        updated = false;
     129              : 
     130            0 :             fdwroutine = GetFdwRoutineForRelation(erm->relation, false);
     131              :             /* this should have been checked already, but let's be safe */
     132            0 :             if (fdwroutine->RefetchForeignRow == NULL)
     133            0 :                 ereport(ERROR,
     134              :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     135              :                          errmsg("cannot lock rows in foreign table \"%s\"",
     136              :                                 RelationGetRelationName(erm->relation))));
     137              : 
     138            0 :             fdwroutine->RefetchForeignRow(estate,
     139              :                                           erm,
     140              :                                           datum,
     141              :                                           markSlot,
     142              :                                           &updated);
     143            0 :             if (TupIsNull(markSlot))
     144              :             {
     145              :                 /* couldn't get the lock, so skip this row */
     146            0 :                 goto lnext;
     147              :             }
     148              : 
     149              :             /*
     150              :              * if FDW says tuple was updated before getting locked, we need to
     151              :              * perform EPQ testing to see if quals are still satisfied
     152              :              */
     153            0 :             if (updated)
     154            0 :                 epq_needed = true;
     155              : 
     156            0 :             continue;
     157              :         }
     158              : 
     159              :         /* okay, try to lock (and fetch) the tuple */
     160        81744 :         tid = *((ItemPointer) DatumGetPointer(datum));
     161        81744 :         switch (erm->markType)
     162              :         {
     163         3931 :             case ROW_MARK_EXCLUSIVE:
     164         3931 :                 lockmode = LockTupleExclusive;
     165         3931 :                 break;
     166           39 :             case ROW_MARK_NOKEYEXCLUSIVE:
     167           39 :                 lockmode = LockTupleNoKeyExclusive;
     168           39 :                 break;
     169         1159 :             case ROW_MARK_SHARE:
     170         1159 :                 lockmode = LockTupleShare;
     171         1159 :                 break;
     172        76615 :             case ROW_MARK_KEYSHARE:
     173        76615 :                 lockmode = LockTupleKeyShare;
     174        76615 :                 break;
     175            0 :             default:
     176            0 :                 elog(ERROR, "unsupported rowmark type");
     177              :                 lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
     178              :                 break;
     179              :         }
     180              : 
     181        81744 :         lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
     182        81744 :         if (!IsolationUsesXactSnapshot())
     183        78025 :             lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
     184              : 
     185        81744 :         test = table_tuple_lock(erm->relation, &tid, estate->es_snapshot,
     186              :                                 markSlot, estate->es_output_cid,
     187              :                                 lockmode, erm->waitPolicy,
     188              :                                 lockflags,
     189              :                                 &tmfd);
     190              : 
     191        81727 :         switch (test)
     192              :         {
     193           37 :             case TM_WouldBlock:
     194              :                 /* couldn't lock tuple in SKIP LOCKED mode */
     195           37 :                 goto lnext;
     196              : 
     197            3 :             case TM_SelfModified:
     198              : 
     199              :                 /*
     200              :                  * The target tuple was already updated or deleted by the
     201              :                  * current command, or by a later command in the current
     202              :                  * transaction.  We *must* ignore the tuple in the former
     203              :                  * case, so as to avoid the "Halloween problem" of repeated
     204              :                  * update attempts.  In the latter case it might be sensible
     205              :                  * to fetch the updated tuple instead, but doing so would
     206              :                  * require changing heap_update and heap_delete to not
     207              :                  * complain about updating "invisible" tuples, which seems
     208              :                  * pretty scary (table_tuple_lock will not complain, but few
     209              :                  * callers expect TM_Invisible, and we're not one of them). So
     210              :                  * for now, treat the tuple as deleted and do not process.
     211              :                  */
     212            3 :                 goto lnext;
     213              : 
     214        81657 :             case TM_Ok:
     215              : 
     216              :                 /*
     217              :                  * Got the lock successfully, the locked tuple saved in
     218              :                  * markSlot for, if needed, EvalPlanQual testing below.
     219              :                  */
     220        81657 :                 if (tmfd.traversed)
     221           35 :                     epq_needed = true;
     222        81657 :                 break;
     223              : 
     224           16 :             case TM_Updated:
     225           16 :                 if (IsolationUsesXactSnapshot())
     226           16 :                     ereport(ERROR,
     227              :                             (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
     228              :                              errmsg("could not serialize access due to concurrent update")));
     229            0 :                 elog(ERROR, "unexpected table_tuple_lock status: %u",
     230              :                      test);
     231              :                 break;
     232              : 
     233           14 :             case TM_Deleted:
     234           14 :                 if (IsolationUsesXactSnapshot())
     235            6 :                     ereport(ERROR,
     236              :                             (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
     237              :                              errmsg("could not serialize access due to concurrent update")));
     238              :                 /* tuple was deleted so don't return it */
     239            8 :                 goto lnext;
     240              : 
     241            0 :             case TM_Invisible:
     242            0 :                 elog(ERROR, "attempted to lock invisible tuple");
     243              :                 break;
     244              : 
     245            0 :             default:
     246            0 :                 elog(ERROR, "unrecognized table_tuple_lock status: %u",
     247              :                      test);
     248              :         }
     249              : 
     250              :         /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
     251        81657 :         erm->curCtid = tid;
     252              :     }
     253              : 
     254              :     /*
     255              :      * If we need to do EvalPlanQual testing, do so.
     256              :      */
     257        81608 :     if (epq_needed)
     258              :     {
     259              :         /* Initialize EPQ machinery */
     260           34 :         EvalPlanQualBegin(&node->lr_epqstate);
     261              : 
     262              :         /*
     263              :          * To fetch non-locked source rows the EPQ logic needs to access junk
     264              :          * columns from the tuple being tested.
     265              :          */
     266           34 :         EvalPlanQualSetSlot(&node->lr_epqstate, slot);
     267              : 
     268              :         /*
     269              :          * And finally we can re-evaluate the tuple.
     270              :          */
     271           34 :         slot = EvalPlanQualNext(&node->lr_epqstate);
     272           34 :         if (TupIsNull(slot))
     273              :         {
     274              :             /* Updated tuple fails qual, so ignore it and go on */
     275            9 :             goto lnext;
     276              :         }
     277              :     }
     278              : 
     279              :     /* Got all locks, so return the current tuple */
     280        81599 :     return slot;
     281              : }
     282              : 
     283              : /* ----------------------------------------------------------------
     284              :  *      ExecInitLockRows
     285              :  *
     286              :  *      This initializes the LockRows node state structures and
     287              :  *      the node's subplan.
     288              :  * ----------------------------------------------------------------
     289              :  */
     290              : LockRowsState *
     291         7018 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
     292              : {
     293              :     LockRowsState *lrstate;
     294         7018 :     Plan       *outerPlan = outerPlan(node);
     295              :     List       *epq_arowmarks;
     296              :     ListCell   *lc;
     297              : 
     298              :     /* check for unsupported flags */
     299              :     Assert(!(eflags & EXEC_FLAG_MARK));
     300              : 
     301              :     /*
     302              :      * create state structure
     303              :      */
     304         7018 :     lrstate = makeNode(LockRowsState);
     305         7018 :     lrstate->ps.plan = (Plan *) node;
     306         7018 :     lrstate->ps.state = estate;
     307         7018 :     lrstate->ps.ExecProcNode = ExecLockRows;
     308              : 
     309              :     /*
     310              :      * Miscellaneous initialization
     311              :      *
     312              :      * LockRows nodes never call ExecQual or ExecProject, therefore no
     313              :      * ExprContext is needed.
     314              :      */
     315              : 
     316              :     /*
     317              :      * Initialize result type.
     318              :      */
     319         7018 :     ExecInitResultTypeTL(&lrstate->ps);
     320              : 
     321              :     /*
     322              :      * then initialize outer plan
     323              :      */
     324         7018 :     outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
     325              : 
     326              :     /* node returns unmodified slots from the outer plan */
     327         7018 :     lrstate->ps.resultopsset = true;
     328         7018 :     lrstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(lrstate),
     329              :                                                  &lrstate->ps.resultopsfixed);
     330              : 
     331              :     /*
     332              :      * LockRows nodes do no projections, so initialize projection info for
     333              :      * this node appropriately
     334              :      */
     335         7018 :     lrstate->ps.ps_ProjInfo = NULL;
     336              : 
     337              :     /*
     338              :      * Locate the ExecRowMark(s) that this node is responsible for, and
     339              :      * construct ExecAuxRowMarks for them.  (InitPlan should already have
     340              :      * built the global list of ExecRowMarks.)
     341              :      */
     342         7018 :     lrstate->lr_arowMarks = NIL;
     343         7018 :     epq_arowmarks = NIL;
     344        15258 :     foreach(lc, node->rowMarks)
     345              :     {
     346         8240 :         PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
     347         8240 :         RangeTblEntry *rte = exec_rt_fetch(rc->rti, estate);
     348              :         ExecRowMark *erm;
     349              :         ExecAuxRowMark *aerm;
     350              : 
     351              :         /* ignore "parent" rowmarks; they are irrelevant at runtime */
     352         8240 :         if (rc->isParent)
     353          923 :             continue;
     354              : 
     355              :         /*
     356              :          * Also ignore rowmarks belonging to child tables that have been
     357              :          * pruned in ExecDoInitialPruning().
     358              :          */
     359         7317 :         if (rte->rtekind == RTE_RELATION &&
     360         7316 :             !bms_is_member(rc->rti, estate->es_unpruned_relids))
     361           36 :             continue;
     362              : 
     363              :         /* find ExecRowMark and build ExecAuxRowMark */
     364         7281 :         erm = ExecFindRowMark(estate, rc->rti, false);
     365         7281 :         aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
     366              : 
     367              :         /*
     368              :          * Only locking rowmarks go into our own list.  Non-locking marks are
     369              :          * passed off to the EvalPlanQual machinery.  This is because we don't
     370              :          * want to bother fetching non-locked rows unless we actually have to
     371              :          * do an EPQ recheck.
     372              :          */
     373         7281 :         if (RowMarkRequiresRowShareLock(erm->markType))
     374         7119 :             lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
     375              :         else
     376          162 :             epq_arowmarks = lappend(epq_arowmarks, aerm);
     377              :     }
     378              : 
     379              :     /* Now we have the info needed to set up EPQ state */
     380         7018 :     EvalPlanQualInit(&lrstate->lr_epqstate, estate,
     381              :                      outerPlan, epq_arowmarks, node->epqParam, NIL);
     382              : 
     383         7018 :     return lrstate;
     384              : }
     385              : 
     386              : /* ----------------------------------------------------------------
     387              :  *      ExecEndLockRows
     388              :  *
     389              :  *      This shuts down the subplan and frees resources allocated
     390              :  *      to this node.
     391              :  * ----------------------------------------------------------------
     392              :  */
     393              : void
     394         6959 : ExecEndLockRows(LockRowsState *node)
     395              : {
     396              :     /* We may have shut down EPQ already, but no harm in another call */
     397         6959 :     EvalPlanQualEnd(&node->lr_epqstate);
     398         6959 :     ExecEndNode(outerPlanState(node));
     399         6959 : }
     400              : 
     401              : 
     402              : void
     403            8 : ExecReScanLockRows(LockRowsState *node)
     404              : {
     405            8 :     PlanState  *outerPlan = outerPlanState(node);
     406              : 
     407              :     /*
     408              :      * if chgParam of subnode is not null then plan will be re-scanned by
     409              :      * first ExecProcNode.
     410              :      */
     411            8 :     if (outerPlan->chgParam == NULL)
     412            0 :         ExecReScan(outerPlan);
     413            8 : }
        

Generated by: LCOV version 2.0-1