LCOV - code coverage report
Current view: top level - src/backend/executor - nodeLockRows.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 72.0 % 157 113
Test Date: 2026-01-26 10:56:24 Functions: 75.0 % 4 3
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 40.7 % 123 50

             Branch data     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                 :      400924 : ExecLockRows(PlanState *pstate)
      39                 :             : {
      40                 :      400924 :         LockRowsState *node = castNode(LockRowsState, pstate);
      41                 :      400924 :         TupleTableSlot *slot;
      42                 :      400924 :         EState     *estate;
      43                 :      400924 :         PlanState  *outerPlan;
      44                 :      400924 :         bool            epq_needed;
      45                 :      400924 :         ListCell   *lc;
      46                 :             : 
      47         [ +  - ]:      400924 :         CHECK_FOR_INTERRUPTS();
      48                 :             : 
      49                 :             :         /*
      50                 :             :          * get information from the node
      51                 :             :          */
      52                 :      400924 :         estate = node->ps.state;
      53                 :      400924 :         outerPlan = outerPlanState(node);
      54                 :             : 
      55                 :             :         /*
      56                 :             :          * Get next tuple from subplan, if any.
      57                 :             :          */
      58                 :             : lnext:
      59                 :      400925 :         slot = ExecProcNode(outerPlan);
      60                 :             : 
      61   [ +  +  +  + ]:      400925 :         if (TupIsNull(slot))
      62                 :             :         {
      63                 :             :                 /* Release any resources held by EPQ mechanism before exiting */
      64                 :         379 :                 EvalPlanQualEnd(&node->lr_epqstate);
      65                 :         379 :                 return NULL;
      66                 :             :         }
      67                 :             : 
      68                 :             :         /* We don't need EvalPlanQual unless we get updated tuple version(s) */
      69                 :      400546 :         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   [ +  -  +  +  :      801130 :         foreach(lc, node->lr_arowMarks)
             +  +  -  +  
                      + ]
      76                 :             :         {
      77                 :      400584 :                 ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
      78                 :      400584 :                 ExecRowMark *erm = aerm->rowmark;
      79                 :      400584 :                 Datum           datum;
      80                 :      400584 :                 bool            isNull;
      81                 :      400584 :                 ItemPointerData tid;
      82                 :      400584 :                 TM_FailureData tmfd;
      83                 :      400584 :                 LockTupleMode lockmode;
      84                 :      400584 :                 int                     lockflags = 0;
      85                 :      400584 :                 TM_Result       test;
      86                 :      400584 :                 TupleTableSlot *markSlot;
      87                 :             : 
      88                 :             :                 /* clear any leftover test tuple for this rel */
      89                 :      400584 :                 markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
      90                 :      400584 :                 ExecClearTuple(markSlot);
      91                 :             : 
      92                 :             :                 /* if child rel, must check whether it produced this row */
      93         [ +  + ]:      400584 :                 if (erm->rti != erm->prti)
      94                 :             :                 {
      95                 :         184 :                         Oid                     tableoid;
      96                 :             : 
      97                 :         368 :                         datum = ExecGetJunkAttribute(slot,
      98                 :         184 :                                                                                  aerm->toidAttNo,
      99                 :             :                                                                                  &isNull);
     100                 :             :                         /* shouldn't ever get a null result... */
     101         [ +  - ]:         184 :                         if (isNull)
     102   [ #  #  #  # ]:           0 :                                 elog(ERROR, "tableoid is NULL");
     103                 :         184 :                         tableoid = DatumGetObjectId(datum);
     104                 :             : 
     105         [ +  - ]:         184 :                         Assert(OidIsValid(erm->relid));
     106         [ +  + ]:         184 :                         if (tableoid != erm->relid)
     107                 :             :                         {
     108                 :             :                                 /* this child is inactive right now */
     109                 :          37 :                                 erm->ermActive = false;
     110                 :          37 :                                 ItemPointerSetInvalid(&(erm->curCtid));
     111                 :          37 :                                 continue;
     112                 :             :                         }
     113         [ +  + ]:         184 :                 }
     114                 :      400547 :                 erm->ermActive = true;
     115                 :             : 
     116                 :             :                 /* fetch the tuple's ctid */
     117                 :      801094 :                 datum = ExecGetJunkAttribute(slot,
     118                 :      400547 :                                                                          aerm->ctidAttNo,
     119                 :             :                                                                          &isNull);
     120                 :             :                 /* shouldn't ever get a null result... */
     121         [ +  - ]:      400547 :                 if (isNull)
     122   [ #  #  #  # ]:           0 :                         elog(ERROR, "ctid is NULL");
     123                 :             : 
     124                 :             :                 /* requests for foreign tables must be passed to their FDW */
     125         [ -  + ]:      400547 :                 if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     126                 :             :                 {
     127                 :           0 :                         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                 :           0 :                                                                                   erm,
     140                 :           0 :                                                                                   datum,
     141                 :           0 :                                                                                   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                 :           0 :                 }
     158                 :             : 
     159                 :             :                 /* okay, try to lock (and fetch) the tuple */
     160                 :      400547 :                 tid = *((ItemPointer) DatumGetPointer(datum));
     161   [ +  +  +  +  :      400547 :                 switch (erm->markType)
                      - ]
     162                 :             :                 {
     163                 :             :                         case ROW_MARK_EXCLUSIVE:
     164                 :          22 :                                 lockmode = LockTupleExclusive;
     165                 :          22 :                                 break;
     166                 :             :                         case ROW_MARK_NOKEYEXCLUSIVE:
     167                 :           1 :                                 lockmode = LockTupleNoKeyExclusive;
     168                 :           1 :                                 break;
     169                 :             :                         case ROW_MARK_SHARE:
     170                 :          12 :                                 lockmode = LockTupleShare;
     171                 :          12 :                                 break;
     172                 :             :                         case ROW_MARK_KEYSHARE:
     173                 :      400512 :                                 lockmode = LockTupleKeyShare;
     174                 :      400512 :                                 break;
     175                 :             :                         default:
     176   [ #  #  #  # ]:           0 :                                 elog(ERROR, "unsupported rowmark type");
     177                 :           0 :                                 lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
     178                 :           0 :                                 break;
     179                 :             :                 }
     180                 :             : 
     181                 :      400547 :                 lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
     182         [ +  + ]:      400547 :                 if (!IsolationUsesXactSnapshot())
     183                 :      400546 :                         lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
     184                 :             : 
     185                 :      801094 :                 test = table_tuple_lock(erm->relation, &tid, estate->es_snapshot,
     186                 :      400547 :                                                                 markSlot, estate->es_output_cid,
     187                 :      400547 :                                                                 lockmode, erm->waitPolicy,
     188                 :      400547 :                                                                 lockflags,
     189                 :             :                                                                 &tmfd);
     190                 :             : 
     191   [ -  -  -  +  :      400547 :                 switch (test)
                +  -  - ]
     192                 :             :                 {
     193                 :             :                         case TM_WouldBlock:
     194                 :             :                                 /* couldn't lock tuple in SKIP LOCKED mode */
     195                 :           0 :                                 goto lnext;
     196                 :             : 
     197                 :             :                         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                 :           1 :                                 goto lnext;
     213                 :             : 
     214                 :             :                         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         [ +  - ]:      400546 :                                 if (tmfd.traversed)
     221                 :           0 :                                         epq_needed = true;
     222                 :      400546 :                                 break;
     223                 :             : 
     224                 :             :                         case TM_Updated:
     225         [ #  # ]:           0 :                                 if (IsolationUsesXactSnapshot())
     226   [ #  #  #  # ]:           0 :                                         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                 :           0 :                                 break;
     232                 :             : 
     233                 :             :                         case TM_Deleted:
     234         [ #  # ]:           0 :                                 if (IsolationUsesXactSnapshot())
     235   [ #  #  #  # ]:           0 :                                         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                 :           0 :                                 goto lnext;
     240                 :             : 
     241                 :             :                         case TM_Invisible:
     242   [ #  #  #  # ]:           0 :                                 elog(ERROR, "attempted to lock invisible tuple");
     243                 :           0 :                                 break;
     244                 :             : 
     245                 :             :                         default:
     246   [ #  #  #  # ]:           0 :                                 elog(ERROR, "unrecognized table_tuple_lock status: %u",
     247                 :             :                                          test);
     248                 :           0 :                 }
     249                 :             : 
     250                 :             :                 /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
     251                 :      400546 :                 erm->curCtid = tid;
     252      [ +  +  + ]:      400584 :         }
     253                 :             : 
     254                 :             :         /*
     255                 :             :          * If we need to do EvalPlanQual testing, do so.
     256                 :             :          */
     257         [ +  - ]:      400545 :         if (epq_needed)
     258                 :             :         {
     259                 :             :                 /* Initialize EPQ machinery */
     260                 :           0 :                 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                 :           0 :                 EvalPlanQualSetSlot(&node->lr_epqstate, slot);
     267                 :             : 
     268                 :             :                 /*
     269                 :             :                  * And finally we can re-evaluate the tuple.
     270                 :             :                  */
     271                 :           0 :                 slot = EvalPlanQualNext(&node->lr_epqstate);
     272   [ #  #  #  # ]:           0 :                 if (TupIsNull(slot))
     273                 :             :                 {
     274                 :             :                         /* Updated tuple fails qual, so ignore it and go on */
     275                 :           0 :                         goto lnext;
     276                 :             :                 }
     277                 :           0 :         }
     278                 :             : 
     279                 :             :         /* Got all locks, so return the current tuple */
     280                 :      400545 :         return slot;
     281                 :      400924 : }
     282                 :             : 
     283                 :             : /* ----------------------------------------------------------------
     284                 :             :  *              ExecInitLockRows
     285                 :             :  *
     286                 :             :  *              This initializes the LockRows node state structures and
     287                 :             :  *              the node's subplan.
     288                 :             :  * ----------------------------------------------------------------
     289                 :             :  */
     290                 :             : LockRowsState *
     291                 :      400832 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
     292                 :             : {
     293                 :      400832 :         LockRowsState *lrstate;
     294                 :      400832 :         Plan       *outerPlan = outerPlan(node);
     295                 :      400832 :         List       *epq_arowmarks;
     296                 :      400832 :         ListCell   *lc;
     297                 :             : 
     298                 :             :         /* check for unsupported flags */
     299         [ +  - ]:      400832 :         Assert(!(eflags & EXEC_FLAG_MARK));
     300                 :             : 
     301                 :             :         /*
     302                 :             :          * create state structure
     303                 :             :          */
     304                 :      400832 :         lrstate = makeNode(LockRowsState);
     305                 :      400832 :         lrstate->ps.plan = (Plan *) node;
     306                 :      400832 :         lrstate->ps.state = estate;
     307                 :      400832 :         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                 :      400832 :         ExecInitResultTypeTL(&lrstate->ps);
     320                 :             : 
     321                 :             :         /*
     322                 :             :          * then initialize outer plan
     323                 :             :          */
     324                 :      400832 :         outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
     325                 :             : 
     326                 :             :         /* node returns unmodified slots from the outer plan */
     327                 :      400832 :         lrstate->ps.resultopsset = true;
     328                 :      801664 :         lrstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(lrstate),
     329                 :      400832 :                                                                                                  &lrstate->ps.resultopsfixed);
     330                 :             : 
     331                 :             :         /*
     332                 :             :          * LockRows nodes do no projections, so initialize projection info for
     333                 :             :          * this node appropriately
     334                 :             :          */
     335                 :      400832 :         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                 :      400832 :         lrstate->lr_arowMarks = NIL;
     343                 :      400832 :         epq_arowmarks = NIL;
     344   [ +  -  +  +  :      801950 :         foreach(lc, node->rowMarks)
                   +  + ]
     345                 :             :         {
     346                 :      401118 :                 PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
     347                 :      401118 :                 RangeTblEntry *rte = exec_rt_fetch(rc->rti, estate);
     348                 :      401118 :                 ExecRowMark *erm;
     349                 :      401118 :                 ExecAuxRowMark *aerm;
     350                 :             : 
     351                 :             :                 /* ignore "parent" rowmarks; they are irrelevant at runtime */
     352         [ +  + ]:      401118 :                 if (rc->isParent)
     353                 :         237 :                         continue;
     354                 :             : 
     355                 :             :                 /*
     356                 :             :                  * Also ignore rowmarks belonging to child tables that have been
     357                 :             :                  * pruned in ExecDoInitialPruning().
     358                 :             :                  */
     359   [ +  -  +  + ]:      400881 :                 if (rte->rtekind == RTE_RELATION &&
     360                 :      400881 :                         !bms_is_member(rc->rti, estate->es_unpruned_relids))
     361                 :          12 :                         continue;
     362                 :             : 
     363                 :             :                 /* find ExecRowMark and build ExecAuxRowMark */
     364                 :      400869 :                 erm = ExecFindRowMark(estate, rc->rti, false);
     365                 :      400869 :                 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         [ +  + ]:      400869 :                 if (RowMarkRequiresRowShareLock(erm->markType))
     374                 :      400861 :                         lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
     375                 :             :                 else
     376                 :           8 :                         epq_arowmarks = lappend(epq_arowmarks, aerm);
     377      [ -  +  + ]:      401118 :         }
     378                 :             : 
     379                 :             :         /* Now we have the info needed to set up EPQ state */
     380                 :      801664 :         EvalPlanQualInit(&lrstate->lr_epqstate, estate,
     381                 :      400832 :                                          outerPlan, epq_arowmarks, node->epqParam, NIL);
     382                 :             : 
     383                 :      801664 :         return lrstate;
     384                 :      400832 : }
     385                 :             : 
     386                 :             : /* ----------------------------------------------------------------
     387                 :             :  *              ExecEndLockRows
     388                 :             :  *
     389                 :             :  *              This shuts down the subplan and frees resources allocated
     390                 :             :  *              to this node.
     391                 :             :  * ----------------------------------------------------------------
     392                 :             :  */
     393                 :             : void
     394                 :      400828 : ExecEndLockRows(LockRowsState *node)
     395                 :             : {
     396                 :             :         /* We may have shut down EPQ already, but no harm in another call */
     397                 :      400828 :         EvalPlanQualEnd(&node->lr_epqstate);
     398                 :      400828 :         ExecEndNode(outerPlanState(node));
     399                 :      400828 : }
     400                 :             : 
     401                 :             : 
     402                 :             : void
     403                 :           0 : ExecReScanLockRows(LockRowsState *node)
     404                 :             : {
     405                 :           0 :         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         [ #  # ]:           0 :         if (outerPlan->chgParam == NULL)
     412                 :           0 :                 ExecReScan(outerPlan);
     413                 :           0 : }
        

Generated by: LCOV version 2.3.2-1