aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor')
-rw-r--r--src/backend/executor/Makefile8
-rw-r--r--src/backend/executor/README9
-rw-r--r--src/backend/executor/execAmi.c10
-rw-r--r--src/backend/executor/execCurrent.c5
-rw-r--r--src/backend/executor/execMain.c402
-rw-r--r--src/backend/executor/execProcnode.c16
-rw-r--r--src/backend/executor/execUtils.c10
-rw-r--r--src/backend/executor/nodeLockRows.c342
-rw-r--r--src/backend/executor/nodeSubqueryscan.c5
9 files changed, 558 insertions, 249 deletions
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index cdd71befeb9..8fd13fc3dc7 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -4,7 +4,7 @@
# Makefile for executor
#
# IDENTIFICATION
-# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.30 2009/10/10 01:43:45 tgl Exp $
+# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.31 2009/10/12 18:10:41 tgl Exp $
#
#-------------------------------------------------------------------------
@@ -17,12 +17,12 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
- nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
- nodeModifyTable.o \
+ nodeHashjoin.o nodeIndexscan.o nodeLimit.o nodeLockRows.o \
+ nodeMaterial.o nodeMergejoin.o nodeModifyTable.o \
nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \
nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
- nodeLimit.o nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
+ nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
nodeWindowAgg.o tstoreReceiver.o spi.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/README b/src/backend/executor/README
index 2416ac4d42c..06d05d52311 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -1,4 +1,4 @@
-$PostgreSQL: pgsql/src/backend/executor/README,v 1.9 2009/10/10 01:43:45 tgl Exp $
+$PostgreSQL: pgsql/src/backend/executor/README,v 1.10 2009/10/12 18:10:41 tgl Exp $
The Postgres Executor
=====================
@@ -157,7 +157,8 @@ if need be) and re-evaluate the query qualifications to see if it would
still meet the quals. If so, we regenerate the updated tuple (if we are
doing an UPDATE) from the modified tuple, and finally update/delete the
modified tuple. SELECT FOR UPDATE/SHARE behaves similarly, except that its
-action is just to lock the modified tuple.
+action is just to lock the modified tuple and return results based on that
+version of the tuple.
To implement this checking, we actually re-run the entire query from scratch
for each modified tuple, but with the scan node that sourced the original
@@ -195,5 +196,5 @@ It should be noted also that UPDATE/DELETE expect at most one tuple to
result from the modified query, whereas in the FOR UPDATE case it's possible
for multiple tuples to result (since we could be dealing with a join in
which multiple tuples join to the modified tuple). We want FOR UPDATE to
-lock all relevant tuples, so we pass all tuples output by all the stacked
-recheck queries back to the executor toplevel for locking.
+lock all relevant tuples, so we process all tuples output by all the stacked
+recheck queries.
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 6c2de387efd..ee929299ad3 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.105 2009/10/10 01:43:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.106 2009/10/12 18:10:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -28,6 +28,7 @@
#include "executor/nodeHashjoin.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeLimit.h"
+#include "executor/nodeLockRows.h"
#include "executor/nodeMaterial.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
@@ -232,6 +233,10 @@ ExecReScan(PlanState *node, ExprContext *exprCtxt)
ExecReScanSetOp((SetOpState *) node, exprCtxt);
break;
+ case T_LockRowsState:
+ ExecReScanLockRows((LockRowsState *) node, exprCtxt);
+ break;
+
case T_LimitState:
ExecReScanLimit((LimitState *) node, exprCtxt);
break;
@@ -444,8 +449,9 @@ ExecSupportsBackwardScan(Plan *node)
/* these don't evaluate tlist */
return true;
+ case T_LockRows:
case T_Limit:
- /* doesn't evaluate tlist */
+ /* these don't evaluate tlist */
return ExecSupportsBackwardScan(outerPlan(node));
default:
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index d9ecc973e1e..78ad80db66f 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.10 2009/06/11 14:48:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.11 2009/10/12 18:10:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -155,8 +155,7 @@ execCurrentOf(CurrentOfExpr *cexpr,
* scan node. Fail if it's not there or buried underneath
* aggregation.
*/
- scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc),
- table_oid);
+ scanstate = search_plan_tree(queryDesc->planstate, table_oid);
if (!scanstate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_STATE),
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7c788ea6df4..d03ad094184 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.332 2009/10/10 01:43:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.333 2009/10/12 18:10:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -66,6 +66,8 @@ typedef struct evalPlanQual
Index rti;
EState *estate;
PlanState *planstate;
+ PlanState *origplanstate;
+ TupleTableSlot *resultslot;
struct evalPlanQual *next; /* stack of active PlanQual plans */
struct evalPlanQual *free; /* list of free PlanQual plans */
} evalPlanQual;
@@ -79,7 +81,6 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
long numberTuples,
ScanDirection direction,
DestReceiver *dest);
-static TupleTableSlot *EvalPlanQualNext(EState *estate);
static void EndEvalPlanQual(EState *estate);
static void ExecCheckRTPerms(List *rangeTable);
static void ExecCheckRTEPerms(RangeTblEntry *rte);
@@ -695,21 +696,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
}
/*
- * Detect whether we're doing SELECT INTO. If so, set the es_into_oids
- * flag appropriately so that the plan tree will be initialized with the
- * correct tuple descriptors. (Other SELECT INTO stuff comes later.)
- */
- estate->es_select_into = false;
- if (operation == CMD_SELECT && plannedstmt->intoClause != NULL)
- {
- estate->es_select_into = true;
- estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options);
- }
-
- /*
- * Have to lock relations selected FOR UPDATE/FOR SHARE before we
- * initialize the plan tree, else we'd be doing a lock upgrade. While we
- * are at it, build the ExecRowMark list.
+ * Similarly, we have to lock relations selected FOR UPDATE/FOR SHARE
+ * before we initialize the plan tree, else we'd be risking lock
+ * upgrades. While we are at it, build the ExecRowMark list.
*/
estate->es_rowMarks = NIL;
foreach(l, plannedstmt->rowMarks)
@@ -729,9 +718,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
erm->relation = relation;
erm->rti = rc->rti;
erm->prti = rc->prti;
+ erm->rowmarkId = rc->rowmarkId;
erm->forUpdate = rc->forUpdate;
erm->noWait = rc->noWait;
- /* We'll locate the junk attrs below */
+ /* remaining fields are filled during LockRows plan node init */
erm->ctidAttNo = InvalidAttrNumber;
erm->toidAttNo = InvalidAttrNumber;
ItemPointerSetInvalid(&(erm->curCtid));
@@ -739,6 +729,18 @@ InitPlan(QueryDesc *queryDesc, int eflags)
}
/*
+ * Detect whether we're doing SELECT INTO. If so, set the es_into_oids
+ * flag appropriately so that the plan tree will be initialized with the
+ * correct tuple descriptors. (Other SELECT INTO stuff comes later.)
+ */
+ estate->es_select_into = false;
+ if (operation == CMD_SELECT && plannedstmt->intoClause != NULL)
+ {
+ estate->es_select_into = true;
+ estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options);
+ }
+
+ /*
* Initialize the executor's tuple table to empty.
*/
estate->es_tupleTable = NIL;
@@ -749,7 +751,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_evalPlanQual = NULL;
estate->es_evTupleNull = NULL;
estate->es_evTuple = NULL;
- estate->es_useEvalPlan = false;
/*
* Initialize private state information for each SubPlan. We must do this
@@ -826,37 +827,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
/* Want to return the cleaned tuple type */
tupType = j->jf_cleanTupType;
-
- /* For SELECT FOR UPDATE/SHARE, find the junk attrs now */
- foreach(l, estate->es_rowMarks)
- {
- ExecRowMark *erm = (ExecRowMark *) lfirst(l);
- char resname[32];
-
- /* always need the ctid */
- snprintf(resname, sizeof(resname), "ctid%u",
- erm->prti);
- erm->ctidAttNo = ExecFindJunkAttribute(j, resname);
- if (!AttributeNumberIsValid(erm->ctidAttNo))
- elog(ERROR, "could not find junk \"%s\" column",
- resname);
- /* if child relation, need tableoid too */
- if (erm->rti != erm->prti)
- {
- snprintf(resname, sizeof(resname), "tableoid%u",
- erm->prti);
- erm->toidAttNo = ExecFindJunkAttribute(j, resname);
- if (!AttributeNumberIsValid(erm->toidAttNo))
- elog(ERROR, "could not find junk \"%s\" column",
- resname);
- }
- }
- }
- else
- {
- estate->es_junkFilter = NULL;
- if (estate->es_rowMarks)
- elog(ERROR, "SELECT FOR UPDATE/SHARE, but no junk columns");
}
}
@@ -1190,8 +1160,6 @@ ExecutePlan(EState *estate,
ScanDirection direction,
DestReceiver *dest)
{
- JunkFilter *junkfilter;
- TupleTableSlot *planSlot;
TupleTableSlot *slot;
long current_tuple_count;
@@ -1216,23 +1184,14 @@ ExecutePlan(EState *estate,
/*
* Execute the plan and obtain a tuple
*/
-lnext: ;
- if (estate->es_useEvalPlan)
- {
- planSlot = EvalPlanQualNext(estate);
- if (TupIsNull(planSlot))
- planSlot = ExecProcNode(planstate);
- }
- else
- planSlot = ExecProcNode(planstate);
+ slot = ExecProcNode(planstate);
/*
* if the tuple is null, then we assume there is nothing more to
* process so we just end the loop...
*/
- if (TupIsNull(planSlot))
+ if (TupIsNull(slot))
break;
- slot = planSlot;
/*
* If we have a junk filter, then project a new tuple with the junk
@@ -1241,128 +1200,13 @@ lnext: ;
* Store this new "clean" tuple in the junkfilter's resultSlot.
* (Formerly, we stored it back over the "dirty" tuple, which is WRONG
* because that tuple slot has the wrong descriptor.)
- *
- * But first, extract all the junk information we need.
*/
- if ((junkfilter = estate->es_junkFilter) != NULL)
- {
- /*
- * Process any FOR UPDATE or FOR SHARE locking requested.
- */
- if (estate->es_rowMarks != NIL)
- {
- ListCell *l;
-
- lmark: ;
- foreach(l, estate->es_rowMarks)
- {
- ExecRowMark *erm = lfirst(l);
- Datum datum;
- bool isNull;
- HeapTupleData tuple;
- Buffer buffer;
- ItemPointerData update_ctid;
- TransactionId update_xmax;
- TupleTableSlot *newSlot;
- LockTupleMode lockmode;
- HTSU_Result test;
-
- /* if child rel, must check whether it produced this row */
- if (erm->rti != erm->prti)
- {
- Oid tableoid;
-
- datum = ExecGetJunkAttribute(slot,
- erm->toidAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "tableoid is NULL");
- tableoid = DatumGetObjectId(datum);
-
- if (tableoid != RelationGetRelid(erm->relation))
- {
- /* this child is inactive right now */
- ItemPointerSetInvalid(&(erm->curCtid));
- continue;
- }
- }
-
- /* okay, fetch the tuple by ctid */
- datum = ExecGetJunkAttribute(slot,
- erm->ctidAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "ctid is NULL");
- tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
-
- if (erm->forUpdate)
- lockmode = LockTupleExclusive;
- else
- lockmode = LockTupleShared;
-
- test = heap_lock_tuple(erm->relation, &tuple, &buffer,
- &update_ctid, &update_xmax,
- estate->es_output_cid,
- lockmode, erm->noWait);
- ReleaseBuffer(buffer);
- switch (test)
- {
- case HeapTupleSelfUpdated:
- /* treat it as deleted; do not process */
- goto lnext;
-
- case HeapTupleMayBeUpdated:
- break;
-
- case HeapTupleUpdated:
- if (IsXactIsoLevelSerializable)
- ereport(ERROR,
- (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
- errmsg("could not serialize access due to concurrent update")));
- if (!ItemPointerEquals(&update_ctid,
- &tuple.t_self))
- {
- /* updated, so look at updated version */
- newSlot = EvalPlanQual(estate,
- erm->rti,
- planstate,
- &update_ctid,
- update_xmax);
- if (!TupIsNull(newSlot))
- {
- slot = planSlot = newSlot;
- estate->es_useEvalPlan = true;
- goto lmark;
- }
- }
-
- /*
- * if tuple was deleted or PlanQual failed for
- * updated tuple - we must not return this tuple!
- */
- goto lnext;
-
- default:
- elog(ERROR, "unrecognized heap_lock_tuple status: %u",
- test);
- }
-
- /* Remember tuple TID for WHERE CURRENT OF */
- erm->curCtid = tuple.t_self;
- }
- }
-
- /*
- * Create a new "clean" tuple with all junk attributes removed.
- */
- slot = ExecFilterJunk(junkfilter, slot);
- }
+ if (estate->es_junkFilter != NULL)
+ slot = ExecFilterJunk(estate->es_junkFilter, slot);
/*
* If we are supposed to send the tuple somewhere, do so.
- * (In practice this is probably always the case at this point.)
+ * (In practice, this is probably always the case at this point.)
*/
if (sendTuples)
(*dest->receiveSlot) (slot, dest);
@@ -1509,18 +1353,104 @@ EvalPlanQual(EState *estate, Index rti,
PlanState *subplanstate,
ItemPointer tid, TransactionId priorXmax)
{
- evalPlanQual *epq;
- EState *epqstate;
+ TupleTableSlot *slot;
+ HeapTuple copyTuple;
+
+ Assert(rti != 0);
+
+ /*
+ * Get the updated version of the row; if fail, return NULL.
+ */
+ copyTuple = EvalPlanQualFetch(estate, rti, tid, priorXmax);
+
+ if (copyTuple == NULL)
+ 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. Find or create a PQ stack entry.
+ */
+ EvalPlanQualPush(estate, rti, subplanstate);
+
+ /*
+ * free old RTE' tuple, if any, and store target tuple where relation's
+ * scan node will see it
+ */
+ EvalPlanQualSetTuple(estate, rti, copyTuple);
+
+ /*
+ * Run the EPQ query, but just for one tuple.
+ */
+ slot = EvalPlanQualNext(estate);
+
+ /*
+ * If we got a result, we must copy it out of the EPQ query's local
+ * context before we shut down the EPQ query.
+ */
+ if (TupIsNull(slot))
+ slot = NULL; /* in case we got back an empty slot */
+ else
+ {
+ TupleDesc tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
+ evalPlanQual *epq = estate->es_evalPlanQual;
+
+ if (epq->resultslot == NULL)
+ {
+ epq->resultslot = ExecInitExtraTupleSlot(estate);
+ ExecSetSlotDescriptor(epq->resultslot, tupdesc);
+ }
+ else
+ {
+ TupleDesc oldtupdesc = epq->resultslot->tts_tupleDescriptor;
+
+ ExecSetSlotDescriptor(epq->resultslot, tupdesc);
+ FreeTupleDesc(oldtupdesc);
+ }
+
+ slot = ExecCopySlot(epq->resultslot, slot);
+ }
+
+ /*
+ * Shut it down ...
+ */
+ EvalPlanQualPop(estate, subplanstate);
+
+ return slot;
+}
+
+/*
+ * Fetch a copy of the newest version of an outdated tuple
+ *
+ * estate - executor state data
+ * rti - rangetable index of table containing tuple
+ * *tid - t_ctid from the outdated tuple (ie, next updated version)
+ * priorXmax - t_xmax from the outdated tuple
+ *
+ * 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).
+ *
+ * XXX this does not lock the new row version ... wouldn't it be better if
+ * it did? As-is, caller might have to repeat all its work.
+ */
+HeapTuple
+EvalPlanQualFetch(EState *estate, Index rti,
+ ItemPointer tid, TransactionId priorXmax)
+{
+ HeapTuple copyTuple = NULL;
Relation relation;
HeapTupleData tuple;
- HeapTuple copyTuple = NULL;
SnapshotData SnapshotDirty;
- bool endNode;
Assert(rti != 0);
/*
- * find relation containing target tuple
+ * Find relation containing target tuple --- must be either a result
+ * relation of the query, or a SELECT FOR UPDATE target
*/
if (estate->es_result_relation_info != NULL &&
estate->es_result_relation_info->ri_RangeTableIndex == rti)
@@ -1661,21 +1591,29 @@ EvalPlanQual(EState *estate, Index rti,
}
/*
- * For UPDATE/DELETE we have to return tid of actual row we're executing
- * PQ for.
+ * Return the copied tuple
*/
- *tid = tuple.t_self;
+ return copyTuple;
+}
+
+/*
+ * Push a new level of EPQ state, and prepare to execute the given subplan
+ */
+void
+EvalPlanQualPush(EState *estate, Index rti, PlanState *subplanstate)
+{
+ evalPlanQual *epq;
+ bool endNode;
+
+ Assert(rti != 0);
- /*
- * Need to run a recheck subquery. Find or create a PQ stack entry.
- */
epq = estate->es_evalPlanQual;
endNode = true;
if (epq != NULL && epq->rti == 0)
{
/* Top PQ stack entry is idle, so re-use it */
- Assert(!(estate->es_useEvalPlan) && epq->next == NULL);
+ Assert(epq->next == NULL);
epq->rti = rti;
endNode = false;
}
@@ -1720,6 +1658,8 @@ EvalPlanQual(EState *estate, Index rti,
newepq->free = NULL;
newepq->estate = NULL;
newepq->planstate = NULL;
+ newepq->origplanstate = NULL;
+ newepq->resultslot = NULL;
}
else
{
@@ -1736,6 +1676,7 @@ EvalPlanQual(EState *estate, Index rti,
}
Assert(epq->rti == rti);
+ Assert(estate->es_evalPlanQual == epq);
/*
* Ok - we're requested for the same RTE. Unfortunately we still have to
@@ -1764,7 +1705,20 @@ EvalPlanQual(EState *estate, Index rti,
* es_result_relation_info) and reset locally changeable
* state in the epq (including es_param_exec_vals, es_evTupleNull).
*/
+ epq->origplanstate = subplanstate;
EvalPlanQualStart(epq, estate, subplanstate->plan, epq->next);
+}
+
+/*
+ * Install one test tuple into current EPQ level
+ */
+void
+EvalPlanQualSetTuple(EState *estate, Index rti, HeapTuple tuple)
+{
+ evalPlanQual *epq = estate->es_evalPlanQual;
+ EState *epqstate;
+
+ Assert(rti != 0);
/*
* free old RTE' tuple, if any, and store target tuple where relation's
@@ -1773,12 +1727,13 @@ EvalPlanQual(EState *estate, Index rti,
epqstate = epq->estate;
if (epqstate->es_evTuple[rti - 1] != NULL)
heap_freetuple(epqstate->es_evTuple[rti - 1]);
- epqstate->es_evTuple[rti - 1] = copyTuple;
-
- return EvalPlanQualNext(estate);
+ epqstate->es_evTuple[rti - 1] = tuple;
}
-static TupleTableSlot *
+/*
+ * Fetch the next row (if any) from EvalPlanQual testing
+ */
+TupleTableSlot *
EvalPlanQualNext(EState *estate)
{
evalPlanQual *epq = estate->es_evalPlanQual;
@@ -1787,39 +1742,48 @@ EvalPlanQualNext(EState *estate)
Assert(epq->rti != 0);
-lpqnext:;
oldcontext = MemoryContextSwitchTo(epq->estate->es_query_cxt);
slot = ExecProcNode(epq->planstate);
MemoryContextSwitchTo(oldcontext);
- /*
- * No more tuples for this PQ. Continue previous one.
- */
- if (TupIsNull(slot))
+ return slot;
+}
+
+/*
+ * Shut down and pop the specified level of EvalPlanQual machinery,
+ * plus any levels nested within it
+ */
+void
+EvalPlanQualPop(EState *estate, PlanState *subplanstate)
+{
+ evalPlanQual *epq = estate->es_evalPlanQual;
+
+ for (;;)
{
+ PlanState *epqplanstate = epq->origplanstate;
evalPlanQual *oldepq;
+ Assert(epq->rti != 0);
+
/* stop execution */
EvalPlanQualStop(epq);
+ epq->origplanstate = NULL;
/* pop old PQ from the stack */
oldepq = epq->next;
if (oldepq == NULL)
{
/* this is the first (oldest) PQ - mark as free */
epq->rti = 0;
- estate->es_useEvalPlan = false;
- /* and continue Query execution */
- return NULL;
+ break;
}
Assert(oldepq->rti != 0);
/* push current PQ to freePQ stack */
oldepq->free = epq;
epq = oldepq;
estate->es_evalPlanQual = epq;
- goto lpqnext;
+ if (epqplanstate == subplanstate)
+ break;
}
-
- return slot;
}
static void
@@ -1839,13 +1803,13 @@ EndEvalPlanQual(EState *estate)
/* stop execution */
EvalPlanQualStop(epq);
+ epq->origplanstate = NULL;
/* pop old PQ from the stack */
oldepq = epq->next;
if (oldepq == NULL)
{
/* this is the first (oldest) PQ - mark as free */
epq->rti = 0;
- estate->es_useEvalPlan = false;
break;
}
Assert(oldepq->rti != 0);
@@ -1887,11 +1851,11 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, Plan *planTree,
epqstate->es_snapshot = estate->es_snapshot;
epqstate->es_crosscheck_snapshot = estate->es_crosscheck_snapshot;
epqstate->es_range_table = estate->es_range_table;
+ epqstate->es_junkFilter = estate->es_junkFilter;
epqstate->es_output_cid = estate->es_output_cid;
epqstate->es_result_relations = estate->es_result_relations;
epqstate->es_num_result_relations = estate->es_num_result_relations;
epqstate->es_result_relation_info = estate->es_result_relation_info;
- epqstate->es_junkFilter = estate->es_junkFilter;
/* es_trig_target_relations must NOT be copied */
epqstate->es_param_list_info = estate->es_param_list_info;
if (estate->es_plannedstmt->nParamExec > 0)
@@ -2005,24 +1969,6 @@ EvalPlanQualStop(evalPlanQual *epq)
epq->planstate = NULL;
}
-/*
- * ExecGetActivePlanTree --- get the active PlanState tree from a QueryDesc
- *
- * Ordinarily this is just the one mentioned in the QueryDesc, but if we
- * are looking at a row returned by the EvalPlanQual machinery, we need
- * to look at the subsidiary state instead.
- */
-PlanState *
-ExecGetActivePlanTree(QueryDesc *queryDesc)
-{
- EState *estate = queryDesc->estate;
-
- if (estate && estate->es_useEvalPlan && estate->es_evalPlanQual != NULL)
- return estate->es_evalPlanQual->planstate;
- else
- return queryDesc->planstate;
-}
-
/*
* Support for SELECT INTO (a/k/a CREATE TABLE AS)
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 5339a57b4f0..21b973d3f89 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -12,7 +12,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.67 2009/10/10 01:43:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.68 2009/10/12 18:10:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -91,6 +91,7 @@
#include "executor/nodeHashjoin.h"
#include "executor/nodeIndexscan.h"
#include "executor/nodeLimit.h"
+#include "executor/nodeLockRows.h"
#include "executor/nodeMaterial.h"
#include "executor/nodeMergejoin.h"
#include "executor/nodeModifyTable.h"
@@ -286,6 +287,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_LockRows:
+ result = (PlanState *) ExecInitLockRows((LockRows *) node,
+ estate, eflags);
+ break;
+
case T_Limit:
result = (PlanState *) ExecInitLimit((Limit *) node,
estate, eflags);
@@ -456,6 +462,10 @@ ExecProcNode(PlanState *node)
result = ExecSetOp((SetOpState *) node);
break;
+ case T_LockRowsState:
+ result = ExecLockRows((LockRowsState *) node);
+ break;
+
case T_LimitState:
result = ExecLimit((LimitState *) node);
break;
@@ -676,6 +686,10 @@ ExecEndNode(PlanState *node)
ExecEndSetOp((SetOpState *) node);
break;
+ case T_LockRowsState:
+ ExecEndLockRows((LockRowsState *) node);
+ break;
+
case T_LimitState:
ExecEndLimit((LimitState *) node);
break;
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index f28d8225f7a..4afce5b9526 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.163 2009/10/08 22:34:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.164 2009/10/12 18:10:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -106,14 +106,14 @@ CreateExecutorState(void)
estate->es_crosscheck_snapshot = InvalidSnapshot; /* no crosscheck */
estate->es_range_table = NIL;
+ estate->es_junkFilter = NULL;
+
estate->es_output_cid = (CommandId) 0;
estate->es_result_relations = NULL;
estate->es_num_result_relations = 0;
estate->es_result_relation_info = NULL;
- estate->es_junkFilter = NULL;
-
estate->es_trig_target_relations = NIL;
estate->es_trig_tuple_slot = NULL;
@@ -124,9 +124,10 @@ CreateExecutorState(void)
estate->es_tupleTable = NIL;
+ estate->es_rowMarks = NIL;
+
estate->es_processed = 0;
estate->es_lastoid = InvalidOid;
- estate->es_rowMarks = NIL;
estate->es_instrument = false;
estate->es_select_into = false;
@@ -142,7 +143,6 @@ CreateExecutorState(void)
estate->es_evalPlanQual = NULL;
estate->es_evTupleNull = NULL;
estate->es_evTuple = NULL;
- estate->es_useEvalPlan = false;
/*
* Return the executor state structure
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
new file mode 100644
index 00000000000..80f7e3cdafb
--- /dev/null
+++ b/src/backend/executor/nodeLockRows.c
@@ -0,0 +1,342 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeLockRows.c
+ * Routines to handle FOR UPDATE/FOR SHARE row locking
+ *
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/backend/executor/nodeLockRows.c,v 1.1 2009/10/12 18:10:43 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ * ExecLockRows - fetch locked rows
+ * ExecInitLockRows - initialize node and subnodes..
+ * ExecEndLockRows - shutdown node and subnodes
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "executor/executor.h"
+#include "executor/nodeLockRows.h"
+#include "storage/bufmgr.h"
+
+
+/* ----------------------------------------------------------------
+ * ExecLockRows
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot * /* return: a tuple or NULL */
+ExecLockRows(LockRowsState *node)
+{
+ TupleTableSlot *slot;
+ EState *estate;
+ PlanState *outerPlan;
+ bool epq_pushed;
+ ListCell *lc;
+
+ /*
+ * get information from the node
+ */
+ estate = node->ps.state;
+ outerPlan = outerPlanState(node);
+
+ /*
+ * Get next tuple from subplan, if any; but if we are evaluating
+ * an EvalPlanQual substitution, first finish that.
+ */
+lnext:
+ if (node->lr_useEvalPlan)
+ {
+ slot = EvalPlanQualNext(estate);
+ if (TupIsNull(slot))
+ {
+ EvalPlanQualPop(estate, outerPlan);
+ node->lr_useEvalPlan = false;
+ slot = ExecProcNode(outerPlan);
+ }
+ }
+ else
+ slot = ExecProcNode(outerPlan);
+
+ if (TupIsNull(slot))
+ return NULL;
+
+ /*
+ * Attempt to lock the source tuple(s).
+ */
+ epq_pushed = false;
+ foreach(lc, node->lr_rowMarks)
+ {
+ ExecRowMark *erm = (ExecRowMark *) lfirst(lc);
+ Datum datum;
+ bool isNull;
+ HeapTupleData tuple;
+ Buffer buffer;
+ ItemPointerData update_ctid;
+ TransactionId update_xmax;
+ LockTupleMode lockmode;
+ HTSU_Result test;
+ HeapTuple copyTuple;
+
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(slot,
+ erm->toidAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "tableoid is NULL");
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ ItemPointerSetInvalid(&(erm->curCtid));
+ continue;
+ }
+ }
+
+ /* fetch the tuple's ctid */
+ datum = ExecGetJunkAttribute(slot,
+ erm->ctidAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "ctid is NULL");
+ tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
+
+ /* okay, try to lock the tuple */
+ if (erm->forUpdate)
+ lockmode = LockTupleExclusive;
+ else
+ lockmode = LockTupleShared;
+
+ test = heap_lock_tuple(erm->relation, &tuple, &buffer,
+ &update_ctid, &update_xmax,
+ estate->es_output_cid,
+ lockmode, erm->noWait);
+ ReleaseBuffer(buffer);
+ switch (test)
+ {
+ case HeapTupleSelfUpdated:
+ /* treat it as deleted; do not process */
+ if (epq_pushed)
+ EvalPlanQualPop(estate, outerPlan);
+ goto lnext;
+
+ case HeapTupleMayBeUpdated:
+ /* got the lock successfully */
+ break;
+
+ case HeapTupleUpdated:
+ if (IsXactIsoLevelSerializable)
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to concurrent update")));
+ if (ItemPointerEquals(&update_ctid,
+ &tuple.t_self))
+ {
+ /* Tuple was deleted, so don't return it */
+ if (epq_pushed)
+ EvalPlanQualPop(estate, outerPlan);
+ goto lnext;
+ }
+
+ /* updated, so look at updated version */
+ copyTuple = EvalPlanQualFetch(estate, erm->rti,
+ &update_ctid, update_xmax);
+
+ if (copyTuple == NULL)
+ {
+ /* Tuple was deleted, so don't return it */
+ if (epq_pushed)
+ EvalPlanQualPop(estate, outerPlan);
+ goto lnext;
+ }
+
+ /*
+ * Need to run a recheck subquery.
+ * Find or create a PQ stack entry.
+ */
+ if (!epq_pushed)
+ {
+ EvalPlanQualPush(estate, erm->rti, outerPlan);
+ epq_pushed = true;
+ }
+
+ /* Store target tuple for relation's scan node */
+ EvalPlanQualSetTuple(estate, erm->rti, copyTuple);
+
+ /* Continue loop until we have all target tuples */
+ break;
+
+ default:
+ elog(ERROR, "unrecognized heap_lock_tuple status: %u",
+ test);
+ }
+
+ /* Remember locked tuple's TID for WHERE CURRENT OF */
+ erm->curCtid = tuple.t_self;
+ }
+
+ /* If we need to do EvalPlanQual testing, loop back to do that */
+ if (epq_pushed)
+ {
+ node->lr_useEvalPlan = true;
+ goto lnext;
+ }
+
+ /* Got all locks, so return the current tuple */
+ return slot;
+}
+
+/* ----------------------------------------------------------------
+ * ExecInitLockRows
+ *
+ * This initializes the LockRows node state structures and
+ * the node's subplan.
+ * ----------------------------------------------------------------
+ */
+LockRowsState *
+ExecInitLockRows(LockRows *node, EState *estate, int eflags)
+{
+ LockRowsState *lrstate;
+ Plan *outerPlan;
+ JunkFilter *j;
+ ListCell *lc;
+
+ /* check for unsupported flags */
+ Assert(!(eflags & EXEC_FLAG_MARK));
+
+ /*
+ * create state structure
+ */
+ lrstate = makeNode(LockRowsState);
+ lrstate->ps.plan = (Plan *) node;
+ lrstate->ps.state = estate;
+ lrstate->lr_useEvalPlan = false;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * LockRows nodes never call ExecQual or ExecProject.
+ */
+
+ /*
+ * Tuple table initialization (XXX not actually used...)
+ */
+ ExecInitResultTupleSlot(estate, &lrstate->ps);
+
+ /*
+ * then initialize outer plan
+ */
+ outerPlan = outerPlan(node);
+ outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
+
+ /*
+ * LockRows nodes do no projections, so initialize projection info for this
+ * node appropriately
+ */
+ ExecAssignResultTypeFromTL(&lrstate->ps);
+ lrstate->ps.ps_ProjInfo = NULL;
+
+ /*
+ * Initialize a junkfilter that we'll use to extract the ctid junk
+ * attributes. (We won't actually apply the filter to remove the
+ * junk, we just pass the rows on as-is. This is because the
+ * junkfilter isn't smart enough to not remove junk attrs that
+ * might be needed further up.)
+ */
+ j = ExecInitJunkFilter(outerPlan->targetlist, false,
+ ExecInitExtraTupleSlot(estate));
+ lrstate->lr_junkFilter = j;
+
+ /*
+ * Locate the ExecRowMark(s) that this node is responsible for.
+ * (InitPlan should already have built the global list of ExecRowMarks.)
+ */
+ lrstate->lr_rowMarks = NIL;
+ foreach(lc, node->rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(lc);
+ ExecRowMark *erm = NULL;
+ char resname[32];
+ ListCell *lce;
+
+ /* ignore "parent" rowmarks; they are irrelevant at runtime */
+ if (rc->isParent)
+ continue;
+
+ foreach(lce, estate->es_rowMarks)
+ {
+ erm = (ExecRowMark *) lfirst(lce);
+ if (erm->rti == rc->rti &&
+ erm->prti == rc->prti &&
+ erm->rowmarkId == rc->rowmarkId)
+ break;
+ erm = NULL;
+ }
+ if (erm == NULL)
+ elog(ERROR, "failed to find ExecRowMark for RowMarkClause");
+ if (AttributeNumberIsValid(erm->ctidAttNo))
+ elog(ERROR, "ExecRowMark is already claimed");
+
+ /* Locate the junk attribute columns in the subplan output */
+
+ /* always need the ctid */
+ snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId);
+ erm->ctidAttNo = ExecFindJunkAttribute(j, resname);
+ if (!AttributeNumberIsValid(erm->ctidAttNo))
+ elog(ERROR, "could not find junk \"%s\" column",
+ resname);
+ /* if child relation, need tableoid too */
+ if (erm->rti != erm->prti)
+ {
+ snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
+ erm->toidAttNo = ExecFindJunkAttribute(j, resname);
+ if (!AttributeNumberIsValid(erm->toidAttNo))
+ elog(ERROR, "could not find junk \"%s\" column",
+ resname);
+ }
+
+ lrstate->lr_rowMarks = lappend(lrstate->lr_rowMarks, erm);
+ }
+
+ return lrstate;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEndLockRows
+ *
+ * This shuts down the subplan and frees resources allocated
+ * to this node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndLockRows(LockRowsState *node)
+{
+ ExecEndNode(outerPlanState(node));
+}
+
+
+void
+ExecReScanLockRows(LockRowsState *node, ExprContext *exprCtxt)
+{
+ node->lr_useEvalPlan = false;
+
+ /*
+ * if chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (((PlanState *) node)->lefttree->chgParam == NULL)
+ ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
+}
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 2599c679d12..15929dedffe 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -12,7 +12,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.41 2009/09/27 21:10:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.42 2009/10/12 18:10:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -99,11 +99,12 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
/*
* SubqueryScan should not have any "normal" children. Also, if planner
- * left anything in subrtable, it's fishy.
+ * left anything in subrtable/subrowmark, it's fishy.
*/
Assert(outerPlan(node) == NULL);
Assert(innerPlan(node) == NULL);
Assert(node->subrtable == NIL);
+ Assert(node->subrowmark == NIL);
/*
* create state structure