LCOV - code coverage report
Current view: top level - src/backend/executor - nodeLockRows.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 83.8 % 130 109
Test Date: 2026-07-10 19:15:41 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 62.2 % 90 56

             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                 :       85611 : ExecLockRows(PlanState *pstate)
      39                 :             : {
      40                 :       85611 :     LockRowsState *node = castNode(LockRowsState, pstate);
      41                 :             :     TupleTableSlot *slot;
      42                 :             :     EState     *estate;
      43                 :             :     PlanState  *outerPlan;
      44                 :             :     bool        epq_needed;
      45                 :             :     ListCell   *lc;
      46                 :             : 
      47         [ -  + ]:       85611 :     CHECK_FOR_INTERRUPTS();
      48                 :             : 
      49                 :             :     /*
      50                 :             :      * get information from the node
      51                 :             :      */
      52                 :       85611 :     estate = node->ps.state;
      53                 :       85611 :     outerPlan = outerPlanState(node);
      54                 :             : 
      55                 :             :     /*
      56                 :             :      * Get next tuple from subplan, if any.
      57                 :             :      */
      58                 :          65 : lnext:
      59                 :       85676 :     slot = ExecProcNode(outerPlan);
      60                 :             : 
      61   [ +  +  +  + ]:       85676 :     if (TupIsNull(slot))
      62                 :             :     {
      63                 :             :         /* Release any resources held by EPQ mechanism before exiting */
      64                 :        4723 :         EvalPlanQualEnd(&node->lr_epqstate);
      65                 :        4723 :         return NULL;
      66                 :             :     }
      67                 :             : 
      68                 :             :     /* We don't need EvalPlanQual unless we get updated tuple version(s) */
      69                 :       80953 :     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   [ +  +  +  +  :      162063 :     foreach(lc, node->lr_arowMarks)
                   +  + ]
      76                 :             :     {
      77                 :       81198 :         ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
      78                 :       81198 :         ExecRowMark *erm = aerm->rowmark;
      79                 :             :         Datum       datum;
      80                 :             :         bool        isNull;
      81                 :             :         ItemPointerData tid;
      82                 :             :         TM_FailureData tmfd;
      83                 :             :         LockTupleMode lockmode;
      84                 :       81198 :         int         lockflags = 0;
      85                 :             :         TM_Result   test;
      86                 :             :         TupleTableSlot *markSlot;
      87                 :             : 
      88                 :             :         /* clear any leftover test tuple for this rel */
      89                 :       81198 :         markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
      90                 :       81198 :         ExecClearTuple(markSlot);
      91                 :             : 
      92                 :             :         /* if child rel, must check whether it produced this row */
      93         [ +  + ]:       81198 :         if (erm->rti != erm->prti)
      94                 :             :         {
      95                 :             :             Oid         tableoid;
      96                 :             : 
      97                 :         989 :             datum = ExecGetJunkAttribute(slot,
      98                 :         989 :                                          aerm->toidAttNo,
      99                 :             :                                          &isNull);
     100                 :             :             /* shouldn't ever get a null result... */
     101         [ -  + ]:         989 :             if (isNull)
     102         [ #  # ]:           0 :                 elog(ERROR, "tableoid is NULL");
     103                 :         989 :             tableoid = DatumGetObjectId(datum);
     104                 :             : 
     105                 :             :             Assert(OidIsValid(erm->relid));
     106         [ +  + ]:         989 :             if (tableoid != erm->relid)
     107                 :             :             {
     108                 :             :                 /* this child is inactive right now */
     109                 :         195 :                 erm->ermActive = false;
     110                 :         195 :                 ItemPointerSetInvalid(&(erm->curCtid));
     111                 :         195 :                 continue;
     112                 :             :             }
     113                 :             :         }
     114                 :       81003 :         erm->ermActive = true;
     115                 :             : 
     116                 :             :         /* fetch the tuple's ctid */
     117                 :       81003 :         datum = ExecGetJunkAttribute(slot,
     118                 :       81003 :                                      aerm->ctidAttNo,
     119                 :             :                                      &isNull);
     120                 :             :         /* shouldn't ever get a null result... */
     121         [ -  + ]:       81003 :         if (isNull)
     122         [ #  # ]:           0 :             elog(ERROR, "ctid is NULL");
     123                 :             : 
     124                 :             :         /* requests for foreign tables must be passed to their FDW */
     125         [ -  + ]:       81003 :         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                 :       81003 :         tid = *((ItemPointer) DatumGetPointer(datum));
     161   [ +  +  +  +  :       81003 :         switch (erm->markType)
                      - ]
     162                 :             :         {
     163                 :        4038 :             case ROW_MARK_EXCLUSIVE:
     164                 :        4038 :                 lockmode = LockTupleExclusive;
     165                 :        4038 :                 break;
     166                 :          40 :             case ROW_MARK_NOKEYEXCLUSIVE:
     167                 :          40 :                 lockmode = LockTupleNoKeyExclusive;
     168                 :          40 :                 break;
     169                 :        1241 :             case ROW_MARK_SHARE:
     170                 :        1241 :                 lockmode = LockTupleShare;
     171                 :        1241 :                 break;
     172                 :       75684 :             case ROW_MARK_KEYSHARE:
     173                 :       75684 :                 lockmode = LockTupleKeyShare;
     174                 :       75684 :                 break;
     175                 :           0 :             default:
     176         [ #  # ]:           0 :                 elog(ERROR, "unsupported rowmark type");
     177                 :             :                 lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
     178                 :             :                 break;
     179                 :             :         }
     180                 :             : 
     181                 :       81003 :         lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
     182         [ +  + ]:       81003 :         if (!IsolationUsesXactSnapshot())
     183                 :       77301 :             lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
     184                 :             : 
     185                 :       81003 :         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   [ +  +  +  +  :       80989 :         switch (test)
                +  -  - ]
     192                 :             :         {
     193                 :          37 :             case TM_WouldBlock:
     194                 :             :                 /* couldn't lock tuple in SKIP LOCKED mode */
     195                 :          37 :                 goto lnext;
     196                 :             : 
     197                 :           4 :             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                 :           4 :                 goto lnext;
     213                 :             : 
     214                 :       80915 :             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         [ +  + ]:       80915 :                 if (tmfd.traversed)
     221                 :          41 :                     epq_needed = true;
     222                 :       80915 :                 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                 :          17 :             case TM_Deleted:
     234         [ +  + ]:          17 :                 if (IsolationUsesXactSnapshot())
     235         [ +  - ]:           4 :                     ereport(ERROR,
     236                 :             :                             (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
     237                 :             :                              errmsg("could not serialize access due to concurrent delete")));
     238                 :             :                 /* tuple was deleted so don't return it */
     239                 :          13 :                 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                 :       80915 :         erm->curCtid = tid;
     252                 :             :     }
     253                 :             : 
     254                 :             :     /*
     255                 :             :      * If we need to do EvalPlanQual testing, do so.
     256                 :             :      */
     257         [ +  + ]:       80865 :     if (epq_needed)
     258                 :             :     {
     259                 :             :         /* Initialize EPQ machinery */
     260                 :          40 :         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                 :          40 :         EvalPlanQualSetSlot(&node->lr_epqstate, slot);
     267                 :             : 
     268                 :             :         /*
     269                 :             :          * And finally we can re-evaluate the tuple.
     270                 :             :          */
     271                 :          40 :         slot = EvalPlanQualNext(&node->lr_epqstate);
     272   [ +  +  +  + ]:          40 :         if (TupIsNull(slot))
     273                 :             :         {
     274                 :             :             /* Updated tuple fails qual, so ignore it and go on */
     275                 :          11 :             goto lnext;
     276                 :             :         }
     277                 :             :     }
     278                 :             : 
     279                 :             :     /* Got all locks, so return the current tuple */
     280                 :       80854 :     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                 :        6164 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
     292                 :             : {
     293                 :             :     LockRowsState *lrstate;
     294                 :        6164 :     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                 :        6164 :     lrstate = makeNode(LockRowsState);
     305                 :        6164 :     lrstate->ps.plan = (Plan *) node;
     306                 :        6164 :     lrstate->ps.state = estate;
     307                 :        6164 :     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                 :        6164 :     ExecInitResultTypeTL(&lrstate->ps);
     320                 :             : 
     321                 :             :     /*
     322                 :             :      * then initialize outer plan
     323                 :             :      */
     324                 :        6164 :     outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
     325                 :             : 
     326                 :             :     /* node returns unmodified slots from the outer plan */
     327                 :        6164 :     lrstate->ps.resultopsset = true;
     328                 :        6164 :     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                 :        6164 :     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                 :        6164 :     lrstate->lr_arowMarks = NIL;
     343                 :        6164 :     epq_arowmarks = NIL;
     344   [ +  -  +  +  :       13860 :     foreach(lc, node->rowMarks)
                   +  + ]
     345                 :             :     {
     346                 :        7696 :         PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
     347                 :        7696 :         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         [ +  + ]:        7696 :         if (rc->isParent)
     353                 :        1184 :             continue;
     354                 :             : 
     355                 :             :         /*
     356                 :             :          * Also ignore rowmarks belonging to child tables that have been
     357                 :             :          * pruned in ExecDoInitialPruning().
     358                 :             :          */
     359         [ +  + ]:        6512 :         if (rte->rtekind == RTE_RELATION &&
     360         [ +  + ]:        6511 :             !bms_is_member(rc->rti, estate->es_unpruned_relids))
     361                 :          48 :             continue;
     362                 :             : 
     363                 :             :         /* find ExecRowMark and build ExecAuxRowMark */
     364                 :        6464 :         erm = ExecFindRowMark(estate, rc->rti, false);
     365                 :        6464 :         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         [ +  + ]:        6464 :         if (RowMarkRequiresRowShareLock(erm->markType))
     374                 :        6294 :             lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
     375                 :             :         else
     376                 :         170 :             epq_arowmarks = lappend(epq_arowmarks, aerm);
     377                 :             :     }
     378                 :             : 
     379                 :             :     /* Now we have the info needed to set up EPQ state */
     380                 :        6164 :     EvalPlanQualInit(&lrstate->lr_epqstate, estate,
     381                 :             :                      outerPlan, epq_arowmarks, node->epqParam, NIL);
     382                 :             : 
     383                 :        6164 :     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                 :        6106 : ExecEndLockRows(LockRowsState *node)
     395                 :             : {
     396                 :             :     /* We may have shut down EPQ already, but no harm in another call */
     397                 :        6106 :     EvalPlanQualEnd(&node->lr_epqstate);
     398                 :        6106 :     ExecEndNode(outerPlanState(node));
     399                 :        6106 : }
     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