]> granicus.if.org Git - postgresql/commitdiff
Store tuples for EvalPlanQual in slots, rather than as HeapTuples.
authorAndres Freund <andres@anarazel.de>
Fri, 1 Mar 2019 18:37:57 +0000 (10:37 -0800)
committerAndres Freund <andres@anarazel.de>
Fri, 1 Mar 2019 18:37:57 +0000 (10:37 -0800)
For the upcoming pluggable table access methods it's quite
inconvenient to store tuples as HeapTuples, as that'd require
converting tuples from a their native format into HeapTuples. Instead
use slots to manage epq tuples.

To fit into that scheme, change the foreign data wrapper callback
RefetchForeignRow, to store the tuple in a slot. Insist on using the
caller provided slot, so it conveniently can be stored in the
corresponding EPQ slot.  As there is no in core user of
RefetchForeignRow, that change was done blindly, but we plan to test
that soon.

To avoid duplicating that work for row locks, move row locks to just
directly use the EPQ slots - it previously temporarily stored tuples
in LockRowsState.lr_curtuples, but that doesn't seem beneficial, given
we'd possibly end up with a significant number of additional slots.

The behaviour of es_epqTupleSet[rti -1] is now checked by
es_epqTupleSlot[rti -1] != NULL, as that is distinguishable from a
slot containing an empty tuple.

Author: Andres Freund, Haribabu Kommi, Ashutosh Bapat
Discussion: https://postgr.es/m/20180703070645.wchpu5muyto5n647@alap3.anarazel.de

13 files changed:
doc/src/sgml/fdwhandler.sgml
src/backend/executor/execMain.c
src/backend/executor/execScan.c
src/backend/executor/execTuples.c
src/backend/executor/execUtils.c
src/backend/executor/nodeIndexonlyscan.c
src/backend/executor/nodeIndexscan.c
src/backend/executor/nodeLockRows.c
src/backend/executor/nodeModifyTable.c
src/include/executor/executor.h
src/include/executor/tuptable.h
src/include/foreign/fdwapi.h
src/include/nodes/execnodes.h

index 77038f53f9ab85197e6080bf887a53a88367d4d4..81c12e36cd49758813b9349fd4c9e966bf82592c 100644 (file)
@@ -992,29 +992,31 @@ GetForeignRowMarkType(RangeTblEntry *rte,
 
     <para>
 <programlisting>
-HeapTuple
+void
 RefetchForeignRow(EState *estate,
                   ExecRowMark *erm,
                   Datum rowid,
+                  TupleTableSlot *slot,
                   bool *updated);
 </programlisting>
 
-     Re-fetch one tuple from the foreign table, after locking it if required.
+     Re-fetch one tuple slot from the foreign table, after locking it if required.
      <literal>estate</literal> is global execution state for the query.
      <literal>erm</literal> is the <structname>ExecRowMark</structname> struct describing
      the target foreign table and the row lock type (if any) to acquire.
      <literal>rowid</literal> identifies the tuple to be fetched.
-     <literal>updated</literal> is an output parameter.
+     <literal>slot</literal> contains nothing useful upon call, but can be used to
+     hold the returned tuple. <literal>updated</literal> is an output parameter.
     </para>
 
     <para>
-     This function should return a palloc'ed copy of the fetched tuple,
-     or <literal>NULL</literal> if the row lock couldn't be obtained.  The row lock
-     type to acquire is defined by <literal>erm-&gt;markType</literal>, which is the
-     value previously returned by <function>GetForeignRowMarkType</function>.
-     (<literal>ROW_MARK_REFERENCE</literal> means to just re-fetch the tuple without
-     acquiring any lock, and <literal>ROW_MARK_COPY</literal> will never be seen by
-     this routine.)
+     This function should store the tuple into the provided, or clear it if if
+     the row lock couldn't be obtained.  The row lock type to acquire is
+     defined by <literal>erm-&gt;markType</literal>, which is the value
+     previously returned by <function>GetForeignRowMarkType</function>.
+     (<literal>ROW_MARK_REFERENCE</literal> means to just re-fetch the tuple
+     without acquiring any lock, and <literal>ROW_MARK_COPY</literal> will
+     never be seen by this routine.)
     </para>
 
     <para>
@@ -1026,7 +1028,7 @@ RefetchForeignRow(EState *estate,
 
     <para>
      Note that by default, failure to acquire a row lock should result in
-     raising an error; a <literal>NULL</literal> return is only appropriate if
+     raising an error; returning with an empty slot is only appropriate if
      the <literal>SKIP LOCKED</literal> option is specified
      by <literal>erm-&gt;waitPolicy</literal>.
     </para>
index 00d8e8fc585ba02a7373685df7eeeac4522e711c..61be56fe0b72ba7a8f12e6c6bb8f20487057fcbe 100644 (file)
@@ -977,8 +977,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
        estate->es_tupleTable = NIL;
 
        /* mark EvalPlanQual not active */
-       estate->es_epqTuple = NULL;
-       estate->es_epqTupleSet = NULL;
+       estate->es_epqTupleSlot = NULL;
        estate->es_epqScanDone = NULL;
 
        /*
@@ -2441,35 +2440,29 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
                         ItemPointer tid, TransactionId priorXmax)
 {
        TupleTableSlot *slot;
-       HeapTuple       copyTuple;
+       TupleTableSlot *testslot;
 
        Assert(rti > 0);
 
        /*
-        * Get and lock the updated version of the row; if fail, return NULL.
+        * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
         */
-       copyTuple = EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock,
-                                                                 tid, priorXmax);
+       EvalPlanQualBegin(epqstate, estate);
 
-       if (copyTuple == NULL)
+       /*
+        * Get and lock the updated version of the row; if fail, return NULL.
+        */
+       testslot = EvalPlanQualSlot(epqstate, relation, rti);
+       if (!EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock,
+                                                  tid, priorXmax,
+                                                  testslot))
                return NULL;
 
        /*
         * For UPDATE/DELETE we have to return tid of actual row we're executing
         * PQ for.
         */
-       *tid = copyTuple->t_self;
-
-       /*
-        * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
-        */
-       EvalPlanQualBegin(epqstate, estate);
-
-       /*
-        * Free old test tuple, if any, and store new tuple where relation's scan
-        * node will see it
-        */
-       EvalPlanQualSetTuple(epqstate, rti, copyTuple);
+       *tid = testslot->tts_tid;
 
        /*
         * Fetch any non-locked source rows
@@ -2496,7 +2489,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
         * re-used to test a tuple for a different relation.  (Not clear that can
         * really happen, but let's be safe.)
         */
-       EvalPlanQualSetTuple(epqstate, rti, NULL);
+       ExecClearTuple(testslot);
 
        return slot;
 }
@@ -2510,21 +2503,22 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
  *     wait_policy - requested lock wait policy
  *     *tid - t_ctid from the outdated tuple (ie, next updated version)
  *     priorXmax - t_xmax from the outdated tuple
+ *     slot - slot to store newest tuple version
  *
- * Returns a palloc'd copy of the newest tuple version, or NULL if we find
- * that there is no newest version (ie, the row was deleted not updated).
- * We also return NULL if the tuple is locked and the wait policy is to skip
+ * Returns true, with slot containing the newest tuple version, or false if we
+ * find that there is no newest version (ie, the row was deleted not updated).
+ * We also return false if the tuple is locked and the wait policy is to skip
  * such tuples.
  *
  * If successful, we have locked the newest tuple version, so caller does not
  * need to worry about it changing anymore.
  */
-HeapTuple
+bool
 EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                  LockWaitPolicy wait_policy,
-                                 ItemPointer tid, TransactionId priorXmax)
+                                 ItemPointer tid, TransactionId priorXmax,
+                                 TupleTableSlot *slot)
 {
-       HeapTuple       copyTuple = NULL;
        HeapTupleData tuple;
        SnapshotData SnapshotDirty;
 
@@ -2557,7 +2551,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                                                         priorXmax))
                        {
                                ReleaseBuffer(buffer);
-                               return NULL;
+                               return false;
                        }
 
                        /* otherwise xmin should not be dirty... */
@@ -2580,7 +2574,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                                break;
                                        case LockWaitSkip:
                                                if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
-                                                       return NULL;    /* skip instead of waiting */
+                                                       return false;   /* skip instead of waiting */
                                                break;
                                        case LockWaitError:
                                                if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
@@ -2608,7 +2602,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
                        {
                                ReleaseBuffer(buffer);
-                               return NULL;
+                               return false;
                        }
 
                        /*
@@ -2640,7 +2634,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                         * now, treat the tuple as deleted and do not process.
                                         */
                                        ReleaseBuffer(buffer);
-                                       return NULL;
+                                       return false;
 
                                case HeapTupleMayBeUpdated:
                                        /* successfully locked */
@@ -2668,11 +2662,11 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                                continue;
                                        }
                                        /* tuple was deleted, so give up */
-                                       return NULL;
+                                       return false;
 
                                case HeapTupleWouldBlock:
                                        ReleaseBuffer(buffer);
-                                       return NULL;
+                                       return false;
 
                                case HeapTupleInvisible:
                                        elog(ERROR, "attempted to lock invisible tuple");
@@ -2682,14 +2676,14 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                        ReleaseBuffer(buffer);
                                        elog(ERROR, "unrecognized heap_lock_tuple status: %u",
                                                 test);
-                                       return NULL;    /* keep compiler quiet */
+                                       return false;   /* keep compiler quiet */
                        }
 
                        /*
-                        * We got tuple - now copy it for use by recheck query.
+                        * We got tuple - store it for use by the recheck query.
                         */
-                       copyTuple = heap_copytuple(&tuple);
-                       ReleaseBuffer(buffer);
+                       ExecStorePinnedBufferHeapTuple(&tuple, slot, buffer);
+                       ExecMaterializeSlot(slot);
                        break;
                }
 
@@ -2700,7 +2694,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                if (tuple.t_data == NULL)
                {
                        ReleaseBuffer(buffer);
-                       return NULL;
+                       return false;
                }
 
                /*
@@ -2710,7 +2704,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                                                                 priorXmax))
                {
                        ReleaseBuffer(buffer);
-                       return NULL;
+                       return false;
                }
 
                /*
@@ -2737,7 +2731,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                {
                        /* deleted, so forget about it */
                        ReleaseBuffer(buffer);
-                       return NULL;
+                       return false;
                }
 
                /* updated, so look at the updated row */
@@ -2748,10 +2742,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode,
                /* loop back to fetch next in chain */
        }
 
-       /*
-        * Return the copied tuple
-        */
-       return copyTuple;
+       /* signal success */
+       return true;
 }
 
 /*
@@ -2792,38 +2784,36 @@ EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
 }
 
 /*
- * Install one test tuple into EPQ state, or clear test tuple if tuple == NULL
- *
- * NB: passed tuple must be palloc'd; it may get freed later
+ * Return, and create if necessary, a slot for an EPQ test tuple.
  */
-void
-EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple)
+TupleTableSlot *
+EvalPlanQualSlot(EPQState *epqstate,
+                                Relation relation, Index rti)
 {
-       EState     *estate = epqstate->estate;
+       TupleTableSlot **slot;
 
-       Assert(rti > 0);
+       Assert(rti > 0 && rti <= epqstate->estate->es_range_table_size);
+       slot = &epqstate->estate->es_epqTupleSlot[rti - 1];
 
-       /*
-        * free old test tuple, if any, and store new tuple where relation's scan
-        * node will see it
-        */
-       if (estate->es_epqTuple[rti - 1] != NULL)
-               heap_freetuple(estate->es_epqTuple[rti - 1]);
-       estate->es_epqTuple[rti - 1] = tuple;
-       estate->es_epqTupleSet[rti - 1] = true;
-}
+       if (*slot == NULL)
+       {
+               MemoryContext oldcontext;
 
-/*
- * Fetch back the current test tuple (if any) for the specified RTI
- */
-HeapTuple
-EvalPlanQualGetTuple(EPQState *epqstate, Index rti)
-{
-       EState     *estate = epqstate->estate;
+               oldcontext = MemoryContextSwitchTo(epqstate->estate->es_query_cxt);
 
-       Assert(rti > 0);
+               if (relation)
+                       *slot = ExecAllocTableSlot(&epqstate->estate->es_tupleTable,
+                                                                          RelationGetDescr(relation),
+                                                                          &TTSOpsBufferHeapTuple);
+               else
+                       *slot = ExecAllocTableSlot(&epqstate->estate->es_tupleTable,
+                                                                          epqstate->origslot->tts_tupleDescriptor,
+                                                                          &TTSOpsVirtual);
+
+               MemoryContextSwitchTo(oldcontext);
+       }
 
-       return estate->es_epqTuple[rti - 1];
+       return *slot;
 }
 
 /*
@@ -2844,13 +2834,14 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
                ExecRowMark *erm = aerm->rowmark;
                Datum           datum;
                bool            isNull;
-               HeapTupleData tuple;
+               TupleTableSlot *slot;
 
                if (RowMarkRequiresRowShareLock(erm->markType))
                        elog(ERROR, "EvalPlanQual doesn't support locking rowmarks");
 
                /* clear any leftover test tuple for this rel */
-               EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
+               slot = EvalPlanQualSlot(epqstate, erm->relation, erm->rti);
+               ExecClearTuple(slot);
 
                /* if child rel, must check whether it produced this row */
                if (erm->rti != erm->prti)
@@ -2875,8 +2866,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
                if (erm->markType == ROW_MARK_REFERENCE)
                {
-                       HeapTuple       copyTuple;
-
                        Assert(erm->relation != NULL);
 
                        /* fetch the tuple's ctid */
@@ -2900,11 +2889,13 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                                         errmsg("cannot lock rows in foreign table \"%s\"",
                                                                        RelationGetRelationName(erm->relation))));
-                               copyTuple = fdwroutine->RefetchForeignRow(epqstate->estate,
-                                                                                                                 erm,
-                                                                                                                 datum,
-                                                                                                                 &updated);
-                               if (copyTuple == NULL)
+
+                               fdwroutine->RefetchForeignRow(epqstate->estate,
+                                                                                         erm,
+                                                                                         datum,
+                                                                                         slot,
+                                                                                         &updated);
+                               if (TupIsNull(slot))
                                        elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
                                /*
@@ -2916,6 +2907,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
                        else
                        {
                                /* ordinary table, fetch the tuple */
+                               HeapTupleData tuple;
                                Buffer          buffer;
 
                                tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
@@ -2923,18 +2915,13 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
                                                                false, NULL))
                                        elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
-                               /* successful, copy tuple */
-                               copyTuple = heap_copytuple(&tuple);
-                               ReleaseBuffer(buffer);
+                               /* successful, store tuple */
+                               ExecStorePinnedBufferHeapTuple(&tuple, slot, buffer);
+                               ExecMaterializeSlot(slot);
                        }
-
-                       /* store tuple */
-                       EvalPlanQualSetTuple(epqstate, erm->rti, copyTuple);
                }
                else
                {
-                       HeapTupleHeader td;
-
                        Assert(erm->markType == ROW_MARK_COPY);
 
                        /* fetch the whole-row Var for the relation */
@@ -2944,19 +2931,8 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
                        /* non-locked rels could be on the inside of outer joins */
                        if (isNull)
                                continue;
-                       td = DatumGetHeapTupleHeader(datum);
-
-                       /* build a temporary HeapTuple control structure */
-                       tuple.t_len = HeapTupleHeaderGetDatumLength(td);
-                       tuple.t_data = td;
-                       /* relation might be a foreign table, if so provide tableoid */
-                       tuple.t_tableOid = erm->relid;
-                       /* also copy t_ctid in case there's valid data there */
-                       tuple.t_self = td->t_ctid;
-
-                       /* copy and store tuple */
-                       EvalPlanQualSetTuple(epqstate, erm->rti,
-                                                                heap_copytuple(&tuple));
+
+                       ExecStoreHeapTupleDatum(datum, slot);
                }
        }
 }
@@ -3152,17 +3128,14 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
         * sub-rechecks to inherit the values being examined by an outer recheck.
         */
        estate->es_epqScanDone = (bool *) palloc0(rtsize * sizeof(bool));
-       if (parentestate->es_epqTuple != NULL)
+       if (parentestate->es_epqTupleSlot != NULL)
        {
-               estate->es_epqTuple = parentestate->es_epqTuple;
-               estate->es_epqTupleSet = parentestate->es_epqTupleSet;
+               estate->es_epqTupleSlot = parentestate->es_epqTupleSlot;
        }
        else
        {
-               estate->es_epqTuple = (HeapTuple *)
-                       palloc0(rtsize * sizeof(HeapTuple));
-               estate->es_epqTupleSet = (bool *)
-                       palloc0(rtsize * sizeof(bool));
+               estate->es_epqTupleSlot = (TupleTableSlot **)
+                       palloc0(rtsize * sizeof(TupleTableSlot *));
        }
 
        /*
index 79dbc38b1740ef292acbcb5abc5670fbf4764630..881131aff26b07050ec95a36e52db10e63fcb516 100644 (file)
@@ -40,7 +40,7 @@ ExecScanFetch(ScanState *node,
 
        CHECK_FOR_INTERRUPTS();
 
-       if (estate->es_epqTuple != NULL)
+       if (estate->es_epqTupleSlot != NULL)
        {
                /*
                 * We are inside an EvalPlanQual recheck.  Return the test tuple if
@@ -63,7 +63,7 @@ ExecScanFetch(ScanState *node,
                                ExecClearTuple(slot);   /* would not be returned by scan */
                        return slot;
                }
-               else if (estate->es_epqTupleSet[scanrelid - 1])
+               else if (estate->es_epqTupleSlot[scanrelid - 1] != NULL)
                {
                        TupleTableSlot *slot = node->ss_ScanTupleSlot;
 
@@ -73,17 +73,15 @@ ExecScanFetch(ScanState *node,
                        /* Else mark to remember that we shouldn't return more */
                        estate->es_epqScanDone[scanrelid - 1] = true;
 
-                       /* Return empty slot if we haven't got a test tuple */
-                       if (estate->es_epqTuple[scanrelid - 1] == NULL)
-                               return ExecClearTuple(slot);
+                       slot = estate->es_epqTupleSlot[scanrelid - 1];
 
-                       /* Store test tuple in the plan node's scan slot */
-                       ExecForceStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
-                                                                       slot);
+                       /* Return empty slot if we haven't got a test tuple */
+                       if (TupIsNull(slot))
+                               return NULL;
 
                        /* Check if it meets the access-method conditions */
                        if (!(*recheckMtd) (node, slot))
-                               ExecClearTuple(slot);   /* would not be returned by scan */
+                               return ExecClearTuple(slot);    /* would not be returned by scan */
 
                        return slot;
                }
index e1948670490d7fa86156e39bbc4b0ae68d49699a..2a12bd56ea9dd6ba76103bb3f4de051444d4e804 100644 (file)
@@ -1542,6 +1542,32 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
        return ExecStoreVirtualTuple(slot);
 }
 
+/*
+ * Store a HeapTuple in datum form, into a slot. That always requires
+ * deforming it and storing it in virtual form.
+ *
+ * Until the slot is materialized, the contents of the slot depend on the
+ * datum.
+ */
+void
+ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot)
+{
+       HeapTupleData tuple = {0};
+       HeapTupleHeader td;
+
+       td = DatumGetHeapTupleHeader(data);
+
+       tuple.t_len = HeapTupleHeaderGetDatumLength(td);
+       tuple.t_self = td->t_ctid;
+       tuple.t_data = td;
+
+       ExecClearTuple(slot);
+
+       heap_deform_tuple(&tuple, slot->tts_tupleDescriptor,
+                                         slot->tts_values, slot->tts_isnull);
+       ExecStoreVirtualTuple(slot);
+}
+
 /*
  * ExecFetchSlotHeapTuple - fetch HeapTuple representing the slot's content
  *
index 5136269348140e4d9bb06bf19a281f25bfbf6b4f..8e9214833d894754f239308aad5d9a3df6bf8340 100644 (file)
@@ -155,8 +155,7 @@ CreateExecutorState(void)
 
        estate->es_per_tuple_exprcontext = NULL;
 
-       estate->es_epqTuple = NULL;
-       estate->es_epqTupleSet = NULL;
+       estate->es_epqTupleSlot = NULL;
        estate->es_epqScanDone = NULL;
        estate->es_sourceText = NULL;
 
index b3f61dd1fc63417d476e2aebe82b2c3921e343eb..72c04b528f5203cc280211daefebaabf7c34b11b 100644 (file)
@@ -426,7 +426,7 @@ ExecIndexOnlyMarkPos(IndexOnlyScanState *node)
 {
        EState     *estate = node->ss.ps.state;
 
-       if (estate->es_epqTuple != NULL)
+       if (estate->es_epqTupleSlot != NULL)
        {
                /*
                 * We are inside an EvalPlanQual recheck.  If a test tuple exists for
@@ -440,7 +440,7 @@ ExecIndexOnlyMarkPos(IndexOnlyScanState *node)
                Index           scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
 
                Assert(scanrelid > 0);
-               if (estate->es_epqTupleSet[scanrelid - 1])
+               if (estate->es_epqTupleSlot[scanrelid - 1] != NULL)
                {
                        /* Verify the claim above */
                        if (!estate->es_epqScanDone[scanrelid - 1])
@@ -461,13 +461,13 @@ ExecIndexOnlyRestrPos(IndexOnlyScanState *node)
 {
        EState     *estate = node->ss.ps.state;
 
-       if (estate->es_epqTuple != NULL)
+       if (estate->es_epqTupleSlot != NULL)
        {
                /* See comments in ExecIndexOnlyMarkPos */
                Index           scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
 
                Assert(scanrelid > 0);
-               if (estate->es_epqTupleSet[scanrelid - 1])
+               if (estate->es_epqTupleSlot[scanrelid - 1])
                {
                        /* Verify the claim above */
                        if (!estate->es_epqScanDone[scanrelid - 1])
index 8b294378936ecea15084dfa1bb5a1e04437270ad..337b561c2418eaf4ce4b2627644d4289724478e5 100644 (file)
@@ -850,7 +850,7 @@ ExecIndexMarkPos(IndexScanState *node)
 {
        EState     *estate = node->ss.ps.state;
 
-       if (estate->es_epqTuple != NULL)
+       if (estate->es_epqTupleSlot != NULL)
        {
                /*
                 * We are inside an EvalPlanQual recheck.  If a test tuple exists for
@@ -864,7 +864,7 @@ ExecIndexMarkPos(IndexScanState *node)
                Index           scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
 
                Assert(scanrelid > 0);
-               if (estate->es_epqTupleSet[scanrelid - 1])
+               if (estate->es_epqTupleSlot[scanrelid - 1] != NULL)
                {
                        /* Verify the claim above */
                        if (!estate->es_epqScanDone[scanrelid - 1])
@@ -885,13 +885,13 @@ ExecIndexRestrPos(IndexScanState *node)
 {
        EState     *estate = node->ss.ps.state;
 
-       if (estate->es_epqTuple != NULL)
+       if (estate->es_epqTupleSlot != NULL)
        {
                /* See comments in ExecIndexMarkPos */
                Index           scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
 
                Assert(scanrelid > 0);
-               if (estate->es_epqTupleSet[scanrelid - 1])
+               if (estate->es_epqTupleSlot[scanrelid - 1] != NULL)
                {
                        /* Verify the claim above */
                        if (!estate->es_epqScanDone[scanrelid - 1])
index 5a12b445a69754ec258c27c5b09a355cb3c9d136..76f0f9d66e5fda4b8f26e5898fb2e826c96d674e 100644 (file)
@@ -66,6 +66,12 @@ lnext:
        /* We don't need EvalPlanQual unless we get updated tuple version(s) */
        epq_needed = false;
 
+       /*
+        * Initialize EPQ machinery. Need to do that early because source tuples
+        * are stored in slots initialized therein.
+        */
+       EvalPlanQualBegin(&node->lr_epqstate, estate);
+
        /*
         * Attempt to lock the source tuple(s).  (Note we only have locking
         * rowmarks in lr_arowMarks.)
@@ -74,7 +80,6 @@ lnext:
        {
                ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
                ExecRowMark *erm = aerm->rowmark;
-               HeapTuple  *testTuple;
                Datum           datum;
                bool            isNull;
                HeapTupleData tuple;
@@ -82,13 +87,11 @@ lnext:
                HeapUpdateFailureData hufd;
                LockTupleMode lockmode;
                HTSU_Result test;
-               HeapTuple       copyTuple;
+               TupleTableSlot *markSlot;
 
                /* clear any leftover test tuple for this rel */
-               testTuple = &(node->lr_curtuples[erm->rti - 1]);
-               if (*testTuple != NULL)
-                       heap_freetuple(*testTuple);
-               *testTuple = NULL;
+               markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
+               ExecClearTuple(markSlot);
 
                /* if child rel, must check whether it produced this row */
                if (erm->rti != erm->prti)
@@ -135,19 +138,18 @@ lnext:
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                                 errmsg("cannot lock rows in foreign table \"%s\"",
                                                                RelationGetRelationName(erm->relation))));
-                       copyTuple = fdwroutine->RefetchForeignRow(estate,
-                                                                                                         erm,
-                                                                                                         datum,
-                                                                                                         &updated);
-                       if (copyTuple == NULL)
+
+                       fdwroutine->RefetchForeignRow(estate,
+                                                                                 erm,
+                                                                                 datum,
+                                                                                 markSlot,
+                                                                                 &updated);
+                       if (TupIsNull(markSlot))
                        {
                                /* couldn't get the lock, so skip this row */
                                goto lnext;
                        }
 
-                       /* save locked tuple for possible EvalPlanQual testing below */
-                       *testTuple = copyTuple;
-
                        /*
                         * if FDW says tuple was updated before getting locked, we need to
                         * perform EPQ testing to see if quals are still satisfied
@@ -230,11 +232,10 @@ lnext:
                                }
 
                                /* updated, so fetch and lock the updated version */
-                               copyTuple = EvalPlanQualFetch(estate, erm->relation,
-                                                                                         lockmode, erm->waitPolicy,
-                                                                                         &hufd.ctid, hufd.xmax);
-
-                               if (copyTuple == NULL)
+                               if (!EvalPlanQualFetch(estate, erm->relation,
+                                                                          lockmode, erm->waitPolicy,
+                                                                          &hufd.ctid, hufd.xmax,
+                                                                          markSlot))
                                {
                                        /*
                                         * Tuple was deleted; or it's locked and we're under SKIP
@@ -243,10 +244,7 @@ lnext:
                                        goto lnext;
                                }
                                /* remember the actually locked tuple's TID */
-                               tuple.t_self = copyTuple->t_self;
-
-                               /* Save locked tuple for EvalPlanQual testing below */
-                               *testTuple = copyTuple;
+                               tuple.t_self = markSlot->tts_tid;
 
                                /* Remember we need to do EPQ testing */
                                epq_needed = true;
@@ -272,42 +270,34 @@ lnext:
         */
        if (epq_needed)
        {
-               /* Initialize EPQ machinery */
-               EvalPlanQualBegin(&node->lr_epqstate, estate);
-
                /*
-                * Transfer any already-fetched tuples into the EPQ state, and fetch a
-                * copy of any rows that were successfully locked without any update
-                * having occurred.  (We do this in a separate pass so as to avoid
-                * overhead in the common case where there are no concurrent updates.)
-                * Make sure any inactive child rels have NULL test tuples in EPQ.
+                * Fetch a copy of any rows that were successfully locked without any
+                * update having occurred.  (We do this in a separate pass so as to
+                * avoid overhead in the common case where there are no concurrent
+                * updates.)  Make sure any inactive child rels have NULL test tuples
+                * in EPQ.
                 */
                foreach(lc, node->lr_arowMarks)
                {
                        ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
                        ExecRowMark *erm = aerm->rowmark;
+                       TupleTableSlot *markSlot;
                        HeapTupleData tuple;
-                       Buffer          buffer;
+                       Buffer buffer;
+
+                       markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
 
                        /* skip non-active child tables, but clear their test tuples */
                        if (!erm->ermActive)
                        {
                                Assert(erm->rti != erm->prti);  /* check it's child table */
-                               EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL);
+                               ExecClearTuple(markSlot);
                                continue;
                        }
 
                        /* was tuple updated and fetched above? */
-                       if (node->lr_curtuples[erm->rti - 1] != NULL)
-                       {
-                               /* yes, so set it as the EPQ test tuple for this rel */
-                               EvalPlanQualSetTuple(&node->lr_epqstate,
-                                                                        erm->rti,
-                                                                        node->lr_curtuples[erm->rti - 1]);
-                               /* freeing this tuple is now the responsibility of EPQ */
-                               node->lr_curtuples[erm->rti - 1] = NULL;
+                       if (!TupIsNull(markSlot))
                                continue;
-                       }
 
                        /* foreign tables should have been fetched above */
                        Assert(erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE);
@@ -318,11 +308,9 @@ lnext:
                        if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
                                                        false, NULL))
                                elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
-
-                       /* successful, copy and store tuple */
-                       EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti,
-                                                                heap_copytuple(&tuple));
-                       ReleaseBuffer(buffer);
+                       ExecStorePinnedBufferHeapTuple(&tuple, markSlot, buffer);
+                       ExecMaterializeSlot(markSlot);
+                       /* successful, use tuple in slot */
                }
 
                /*
@@ -401,13 +389,6 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
         */
        lrstate->ps.ps_ProjInfo = NULL;
 
-       /*
-        * Create workspace in which we can remember per-RTE locked tuples
-        */
-       lrstate->lr_ntables = estate->es_range_table_size;
-       lrstate->lr_curtuples = (HeapTuple *)
-               palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
-
        /*
         * Locate the ExecRowMark(s) that this node is responsible for, and
         * construct ExecAuxRowMarks for them.  (InitPlan should already have
@@ -425,9 +406,6 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
                if (rc->isParent)
                        continue;
 
-               /* safety check on size of lr_curtuples array */
-               Assert(rc->rti > 0 && rc->rti <= lrstate->lr_ntables);
-
                /* find ExecRowMark and build ExecAuxRowMark */
                erm = ExecFindRowMark(estate, rc->rti, false);
                aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
index 267ecc7c7f769c765e7c8e8b467bb95c137ad643..14d25fd2aa8d5250ae70ae2ae938282d3d83bae8 100644 (file)
@@ -1803,7 +1803,7 @@ ExecModifyTable(PlanState *pstate)
         * case it is within a CTE subplan.  Hence this test must be here, not in
         * ExecInitModifyTable.)
         */
-       if (estate->es_epqTuple != NULL)
+       if (estate->es_epqTupleSlot != NULL)
                elog(ERROR, "ModifyTable should not be called during EvalPlanQual");
 
        /*
index 25b72c59e222b712af824696224554043f91baa8..9003f2ce583539ab2c327b459f131028f5e04767 100644 (file)
@@ -197,16 +197,16 @@ extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
 extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
                         Relation relation, Index rti, LockTupleMode lockmode,
                         ItemPointer tid, TransactionId priorXmax);
-extern HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
+extern bool EvalPlanQualFetch(EState *estate, Relation relation,
                                  LockTupleMode lockmode, LockWaitPolicy wait_policy,
-                                 ItemPointer tid, TransactionId priorXmax);
+                                 ItemPointer tid, TransactionId priorXmax,
+                                 TupleTableSlot *slot);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
                                 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
                                        Plan *subplan, List *auxrowmarks);
-extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
-                                        HeapTuple tuple);
-extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern TupleTableSlot *EvalPlanQualSlot(EPQState *epqstate,
+                        Relation relation, Index rti);
 
 #define EvalPlanQualSetSlot(epqstate, slot)  ((epqstate)->origslot = (slot))
 extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
index 834786620a55c79077c13523f34a294b1a04bc6f..b0561ebe2917b3743fc9589bea8a49f0a1c93a18 100644 (file)
@@ -320,6 +320,7 @@ extern void ExecForceStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot *slot,
                                                                           bool shouldFree);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
+extern void ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot);
 extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
                                                  bool *shouldFree);
index 01c95c3267501d03593d6156cddba4d1586b30dc..fd91eb633e3939cb6602c0b5a11e946c96126c96 100644 (file)
@@ -121,10 +121,11 @@ typedef void (*EndDirectModify_function) (ForeignScanState *node);
 typedef RowMarkType (*GetForeignRowMarkType_function) (RangeTblEntry *rte,
                                                                                                           LockClauseStrength strength);
 
-typedef HeapTuple (*RefetchForeignRow_function) (EState *estate,
-                                                                                                ExecRowMark *erm,
-                                                                                                Datum rowid,
-                                                                                                bool *updated);
+typedef void (*RefetchForeignRow_function) (EState *estate,
+                                                                                       ExecRowMark *erm,
+                                                                                       Datum rowid,
+                                                                                       TupleTableSlot *slot,
+                                                                                       bool *updated);
 
 typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
                                                                                         struct ExplainState *es);
index 09f8217c8002238fb852f9dee56e99e6f693ba7f..996d872c56206e842deb108aea0240d5fc80617c 100644 (file)
@@ -562,15 +562,15 @@ typedef struct EState
 
        /*
         * These fields are for re-evaluating plan quals when an updated tuple is
-        * substituted in READ COMMITTED mode.  es_epqTuple[] contains tuples that
-        * scan plan nodes should return instead of whatever they'd normally
-        * return, or NULL if nothing to return; es_epqTupleSet[] is true if a
-        * particular array entry is valid; and es_epqScanDone[] is state to
-        * remember if the tuple has been returned already.  Arrays are of size
-        * es_range_table_size and are indexed by scan node scanrelid - 1.
+        * substituted in READ COMMITTED mode.  es_epqTupleSlot[] contains test
+        * tuples that scan plan nodes should return instead of whatever they'd
+        * normally return, or an empty slot if there is nothing to return; if
+        * es_epqTupleSlot[] is not NULL if a particular array entry is valid; and
+        * es_epqScanDone[] is state to remember if the tuple has been returned
+        * already.  Arrays are of size es_range_table_size and are indexed by
+        * scan node scanrelid - 1.
         */
-       HeapTuple  *es_epqTuple;        /* array of EPQ substitute tuples */
-       bool       *es_epqTupleSet; /* true if EPQ tuple is provided */
+       TupleTableSlot **es_epqTupleSlot;       /* array of EPQ substitute tuples */
        bool       *es_epqScanDone; /* true if EPQ tuple has been fetched */
 
        bool            es_use_parallel_mode;   /* can we use parallel workers? */
@@ -2257,8 +2257,6 @@ typedef struct LockRowsState
        PlanState       ps;                             /* its first field is NodeTag */
        List       *lr_arowMarks;       /* List of ExecAuxRowMarks */
        EPQState        lr_epqstate;    /* for evaluating EvalPlanQual rechecks */
-       HeapTuple  *lr_curtuples;       /* locked tuples (one entry per RT entry) */
-       int                     lr_ntables;             /* length of lr_curtuples[] array */
 } LockRowsState;
 
 /* ----------------