LCOV - code coverage report
Current view: top level - src/backend/executor - nodeLockRows.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 105 125 84.0 %
Date: 2024-04-26 13:11:04 Functions: 4 4 100.0 %
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-2024, 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       15232 : ExecLockRows(PlanState *pstate)
      39             : {
      40       15232 :     LockRowsState *node = castNode(LockRowsState, pstate);
      41             :     TupleTableSlot *slot;
      42             :     EState     *estate;
      43             :     PlanState  *outerPlan;
      44             :     bool        epq_needed;
      45             :     ListCell   *lc;
      46             : 
      47       15232 :     CHECK_FOR_INTERRUPTS();
      48             : 
      49             :     /*
      50             :      * get information from the node
      51             :      */
      52       15232 :     estate = node->ps.state;
      53       15232 :     outerPlan = outerPlanState(node);
      54             : 
      55             :     /*
      56             :      * Get next tuple from subplan, if any.
      57             :      */
      58       15334 : lnext:
      59       15334 :     slot = ExecProcNode(outerPlan);
      60             : 
      61       15334 :     if (TupIsNull(slot))
      62             :     {
      63             :         /* Release any resources held by EPQ mechanism before exiting */
      64        3116 :         EvalPlanQualEnd(&node->lr_epqstate);
      65        3116 :         return NULL;
      66             :     }
      67             : 
      68             :     /* We don't need EvalPlanQual unless we get updated tuple version(s) */
      69       12218 :     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       24648 :     foreach(lc, node->lr_arowMarks)
      76             :     {
      77       12574 :         ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
      78       12574 :         ExecRowMark *erm = aerm->rowmark;
      79             :         Datum       datum;
      80             :         bool        isNull;
      81             :         ItemPointerData tid;
      82             :         TM_FailureData tmfd;
      83             :         LockTupleMode lockmode;
      84       12574 :         int         lockflags = 0;
      85             :         TM_Result   test;
      86             :         TupleTableSlot *markSlot;
      87             : 
      88             :         /* clear any leftover test tuple for this rel */
      89       12574 :         markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
      90       12574 :         ExecClearTuple(markSlot);
      91             : 
      92             :         /* if child rel, must check whether it produced this row */
      93       12574 :         if (erm->rti != erm->prti)
      94             :         {
      95             :             Oid         tableoid;
      96             : 
      97        1442 :             datum = ExecGetJunkAttribute(slot,
      98        1442 :                                          aerm->toidAttNo,
      99             :                                          &isNull);
     100             :             /* shouldn't ever get a null result... */
     101        1442 :             if (isNull)
     102           0 :                 elog(ERROR, "tableoid is NULL");
     103        1442 :             tableoid = DatumGetObjectId(datum);
     104             : 
     105             :             Assert(OidIsValid(erm->relid));
     106        1442 :             if (tableoid != erm->relid)
     107             :             {
     108             :                 /* this child is inactive right now */
     109         322 :                 erm->ermActive = false;
     110         322 :                 ItemPointerSetInvalid(&(erm->curCtid));
     111         322 :                 continue;
     112             :             }
     113             :         }
     114       12252 :         erm->ermActive = true;
     115             : 
     116             :         /* fetch the tuple's ctid */
     117       12252 :         datum = ExecGetJunkAttribute(slot,
     118       12252 :                                      aerm->ctidAttNo,
     119             :                                      &isNull);
     120             :         /* shouldn't ever get a null result... */
     121       12252 :         if (isNull)
     122           0 :             elog(ERROR, "ctid is NULL");
     123             : 
     124             :         /* requests for foreign tables must be passed to their FDW */
     125       12252 :         if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     126             :         {
     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       12252 :         tid = *((ItemPointer) DatumGetPointer(datum));
     161       12252 :         switch (erm->markType)
     162             :         {
     163        3636 :             case ROW_MARK_EXCLUSIVE:
     164        3636 :                 lockmode = LockTupleExclusive;
     165        3636 :                 break;
     166          62 :             case ROW_MARK_NOKEYEXCLUSIVE:
     167          62 :                 lockmode = LockTupleNoKeyExclusive;
     168          62 :                 break;
     169        2292 :             case ROW_MARK_SHARE:
     170        2292 :                 lockmode = LockTupleShare;
     171        2292 :                 break;
     172        6262 :             case ROW_MARK_KEYSHARE:
     173        6262 :                 lockmode = LockTupleKeyShare;
     174        6262 :                 break;
     175           0 :             default:
     176           0 :                 elog(ERROR, "unsupported rowmark type");
     177             :                 lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
     178             :                 break;
     179             :         }
     180             : 
     181       12252 :         lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
     182       12252 :         if (!IsolationUsesXactSnapshot())
     183        9026 :             lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
     184             : 
     185       12252 :         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       12226 :         switch (test)
     192             :         {
     193          74 :             case TM_WouldBlock:
     194             :                 /* couldn't lock tuple in SKIP LOCKED mode */
     195          74 :                 goto lnext;
     196             : 
     197           6 :             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           6 :                 goto lnext;
     213             : 
     214       12108 :             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       12108 :                 if (tmfd.traversed)
     221          56 :                     epq_needed = true;
     222       12108 :                 break;
     223             : 
     224          24 :             case TM_Updated:
     225          24 :                 if (IsolationUsesXactSnapshot())
     226          24 :                     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           4 :                     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          10 :                 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       12108 :         erm->curCtid = tid;
     252             :     }
     253             : 
     254             :     /*
     255             :      * If we need to do EvalPlanQual testing, do so.
     256             :      */
     257       12074 :     if (epq_needed)
     258             :     {
     259             :         /* Initialize EPQ machinery */
     260          54 :         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          54 :         EvalPlanQualSetSlot(&node->lr_epqstate, slot);
     267             : 
     268             :         /*
     269             :          * And finally we can re-evaluate the tuple.
     270             :          */
     271          54 :         slot = EvalPlanQualNext(&node->lr_epqstate);
     272          54 :         if (TupIsNull(slot))
     273             :         {
     274             :             /* Updated tuple fails qual, so ignore it and go on */
     275          12 :             goto lnext;
     276             :         }
     277             :     }
     278             : 
     279             :     /* Got all locks, so return the current tuple */
     280       12062 :     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        7914 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
     292             : {
     293             :     LockRowsState *lrstate;
     294        7914 :     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        7914 :     lrstate = makeNode(LockRowsState);
     305        7914 :     lrstate->ps.plan = (Plan *) node;
     306        7914 :     lrstate->ps.state = estate;
     307        7914 :     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        7914 :     ExecInitResultTypeTL(&lrstate->ps);
     320             : 
     321             :     /*
     322             :      * then initialize outer plan
     323             :      */
     324        7914 :     outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
     325             : 
     326             :     /* node returns unmodified slots from the outer plan */
     327        7914 :     lrstate->ps.resultopsset = true;
     328        7914 :     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        7914 :     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        7914 :     lrstate->lr_arowMarks = NIL;
     343        7914 :     epq_arowmarks = NIL;
     344       17930 :     foreach(lc, node->rowMarks)
     345             :     {
     346       10016 :         PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
     347             :         ExecRowMark *erm;
     348             :         ExecAuxRowMark *aerm;
     349             : 
     350             :         /* ignore "parent" rowmarks; they are irrelevant at runtime */
     351       10016 :         if (rc->isParent)
     352        1648 :             continue;
     353             : 
     354             :         /* find ExecRowMark and build ExecAuxRowMark */
     355        8368 :         erm = ExecFindRowMark(estate, rc->rti, false);
     356        8368 :         aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
     357             : 
     358             :         /*
     359             :          * Only locking rowmarks go into our own list.  Non-locking marks are
     360             :          * passed off to the EvalPlanQual machinery.  This is because we don't
     361             :          * want to bother fetching non-locked rows unless we actually have to
     362             :          * do an EPQ recheck.
     363             :          */
     364        8368 :         if (RowMarkRequiresRowShareLock(erm->markType))
     365        8100 :             lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
     366             :         else
     367         268 :             epq_arowmarks = lappend(epq_arowmarks, aerm);
     368             :     }
     369             : 
     370             :     /* Now we have the info needed to set up EPQ state */
     371        7914 :     EvalPlanQualInit(&lrstate->lr_epqstate, estate,
     372             :                      outerPlan, epq_arowmarks, node->epqParam, NIL);
     373             : 
     374        7914 :     return lrstate;
     375             : }
     376             : 
     377             : /* ----------------------------------------------------------------
     378             :  *      ExecEndLockRows
     379             :  *
     380             :  *      This shuts down the subplan and frees resources allocated
     381             :  *      to this node.
     382             :  * ----------------------------------------------------------------
     383             :  */
     384             : void
     385        7828 : ExecEndLockRows(LockRowsState *node)
     386             : {
     387             :     /* We may have shut down EPQ already, but no harm in another call */
     388        7828 :     EvalPlanQualEnd(&node->lr_epqstate);
     389        7828 :     ExecEndNode(outerPlanState(node));
     390        7828 : }
     391             : 
     392             : 
     393             : void
     394          16 : ExecReScanLockRows(LockRowsState *node)
     395             : {
     396          16 :     PlanState  *outerPlan = outerPlanState(node);
     397             : 
     398             :     /*
     399             :      * if chgParam of subnode is not null then plan will be re-scanned by
     400             :      * first ExecProcNode.
     401             :      */
     402          16 :     if (outerPlan->chgParam == NULL)
     403           0 :         ExecReScan(outerPlan);
     404          16 : }

Generated by: LCOV version 1.14