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

Generated by: LCOV version 1.14