]> granicus.if.org Git - postgresql/commitdiff
Move the handling of SELECT FOR UPDATE locking and rechecking out of
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 12 Oct 2009 18:10:51 +0000 (18:10 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 12 Oct 2009 18:10:51 +0000 (18:10 +0000)
execMain.c and into a new plan node type LockRows.  Like the recent change
to put table updating into a ModifyTable plan node, this increases planning
flexibility by allowing the operations to occur below the top level of the
plan tree.  It's necessary in any case to restore the previous behavior of
having FOR UPDATE locking occur before ModifyTable does.

This partially refactors EvalPlanQual to allow multiple rows-under-test
to be inserted into the EPQ machinery before starting an EPQ test query.
That isn't sufficient to fix EPQ's general bogosity in the face of plans
that return multiple rows per test row, though.  Since this patch is
mostly about getting some plan node infrastructure in place and not about
fixing ten-year-old bugs, I will leave EPQ improvements for another day.

Another behavioral change that we could now think about is doing FOR UPDATE
before LIMIT, but that too seems like it should be treated as a followon
patch.

32 files changed:
src/backend/commands/explain.c
src/backend/executor/Makefile
src/backend/executor/README
src/backend/executor/execAmi.c
src/backend/executor/execCurrent.c
src/backend/executor/execMain.c
src/backend/executor/execProcnode.c
src/backend/executor/execUtils.c
src/backend/executor/nodeLockRows.c [new file with mode: 0644]
src/backend/executor/nodeSubqueryscan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/preptlist.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/relnode.c
src/backend/parser/analyze.c
src/include/catalog/catversion.h
src/include/executor/executor.h
src/include/executor/nodeLockRows.h [new file with mode: 0644]
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/relation.h
src/include/optimizer/planmain.h

index 048d12f97a21dc571188731cae4ca7ec5ddd4795..1260ca00c237d22f731a9c3dc1b641a9da82f3db 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.191 2009/10/10 01:43:45 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.192 2009/10/12 18:10:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -718,6 +718,9 @@ ExplainNode(Plan *plan, PlanState *planstate,
                                        break;
                        }
                        break;
+               case T_LockRows:
+                       pname = sname = "LockRows";
+                       break;
                case T_Limit:
                        pname = sname = "Limit";
                        break;
index cdd71befeb90bb68931af8d9c48ca1746b7848d1..8fd13fc3dc7e7c3472d4bfa276053d700630adc6 100644 (file)
@@ -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
index 2416ac4d42c6f50a959e5c1ec9515e6df5dbb511..06d05d523118545ac1e1a4e04ccb0bc798cd7aca 100644 (file)
@@ -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.
index 6c2de387efda01c65c6a355a7842b5315e8e7984..ee929299ad36e134673c1415599ba83a25958ec5 100644 (file)
@@ -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:
index d9ecc973e1e99840b274fa037de97cebab6980e8..78ad80db66fe1b1cce7c8184605952019503ece4 100644 (file)
@@ -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),
index 7c788ea6df472e22f32cbd699cae135a9a9fb5cc..d03ad0941848b2d4129c3a210b9cb0692d06d485 100644 (file)
@@ -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,15 +718,28 @@ 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));
                estate->es_rowMarks = lappend(estate->es_rowMarks, erm);
        }
 
+       /*
+        * 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.
         */
@@ -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)
index 5339a57b4f0f19e4648e5c22ac7ddd791d2325ab..21b973d3f8966e1350776e88c9480cfd93806162 100644 (file)
@@ -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;
index f28d8225f7a1f798039ce0aa1a993572cb36c220..4afce5b95261697384253071bd3a9fc19935d9e3 100644 (file)
@@ -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 (file)
index 0000000..80f7e3c
--- /dev/null
@@ -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);
+}
index 2599c679d122542b19dabee7967267dc0842afee..15929dedffeb4ad836d6b18498169a8d245cf06b 100644 (file)
@@ -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
index cee7cb8ded6bfb7009d1d44ced684569c5d8b907..eef333b7187924cee622a80c5d4478f71aff3b6f 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.443 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.444 2009/10/12 18:10:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -423,6 +423,7 @@ _copySubqueryScan(SubqueryScan *from)
         */
        COPY_NODE_FIELD(subplan);
        COPY_NODE_FIELD(subrtable);
+       COPY_NODE_FIELD(subrowmark);
 
        return newnode;
 }
@@ -794,6 +795,27 @@ _copySetOp(SetOp *from)
        return newnode;
 }
 
+/*
+ * _copyLockRows
+ */
+static LockRows *
+_copyLockRows(LockRows *from)
+{
+       LockRows           *newnode = makeNode(LockRows);
+
+       /*
+        * copy node superclass fields
+        */
+       CopyPlanFields((Plan *) from, (Plan *) newnode);
+
+       /*
+        * copy remainder of node
+        */
+       COPY_NODE_FIELD(rowMarks);
+
+       return newnode;
+}
+
 /*
  * _copyLimit
  */
@@ -1813,6 +1835,7 @@ _copyRowMarkClause(RowMarkClause *from)
 
        COPY_SCALAR_FIELD(rti);
        COPY_SCALAR_FIELD(prti);
+       COPY_SCALAR_FIELD(rowmarkId);
        COPY_SCALAR_FIELD(forUpdate);
        COPY_SCALAR_FIELD(noWait);
        COPY_SCALAR_FIELD(isParent);
@@ -3589,6 +3612,9 @@ copyObject(void *from)
                case T_SetOp:
                        retval = _copySetOp(from);
                        break;
+               case T_LockRows:
+                       retval = _copyLockRows(from);
+                       break;
                case T_Limit:
                        retval = _copyLimit(from);
                        break;
index 5766f37c14394ac0db80f76e1410ebc0f36a7897..ef8abc4773f3dd902264528f8a206080dac2acc3 100644 (file)
@@ -22,7 +22,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.365 2009/10/08 02:39:20 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.366 2009/10/12 18:10:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2194,6 +2194,7 @@ _equalRowMarkClause(RowMarkClause *a, RowMarkClause *b)
 {
        COMPARE_SCALAR_FIELD(rti);
        COMPARE_SCALAR_FIELD(prti);
+       COMPARE_SCALAR_FIELD(rowmarkId);
        COMPARE_SCALAR_FIELD(forUpdate);
        COMPARE_SCALAR_FIELD(noWait);
        COMPARE_SCALAR_FIELD(isParent);
index a776f9fe3e241eba189b927ceb5c32ee3fad3724..e6952737c42dbc1cb2d78d00bb358f5b6de86e6e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.367 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.368 2009/10/12 18:10:45 tgl Exp $
  *
  * NOTES
  *       Every node type that can appear in stored rules' parsetrees *must*
@@ -454,6 +454,7 @@ _outSubqueryScan(StringInfo str, SubqueryScan *node)
 
        WRITE_NODE_FIELD(subplan);
        WRITE_NODE_FIELD(subrtable);
+       WRITE_NODE_FIELD(subrowmark);
 }
 
 static void
@@ -720,6 +721,16 @@ _outSetOp(StringInfo str, SetOp *node)
        WRITE_LONG_FIELD(numGroups);
 }
 
+static void
+_outLockRows(StringInfo str, LockRows *node)
+{
+       WRITE_NODE_TYPE("LOCKROWS");
+
+       _outPlanInfo(str, (Plan *) node);
+
+       WRITE_NODE_FIELD(rowMarks);
+}
+
 static void
 _outLimit(StringInfo str, Limit *node)
 {
@@ -1494,11 +1505,14 @@ _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
        WRITE_NODE_FIELD(paramlist);
        WRITE_NODE_FIELD(subplans);
        WRITE_NODE_FIELD(subrtables);
+       WRITE_NODE_FIELD(subrowmarks);
        WRITE_BITMAPSET_FIELD(rewindPlanIDs);
        WRITE_NODE_FIELD(finalrtable);
+       WRITE_NODE_FIELD(finalrowmarks);
        WRITE_NODE_FIELD(relationOids);
        WRITE_NODE_FIELD(invalItems);
        WRITE_UINT_FIELD(lastPHId);
+       WRITE_UINT_FIELD(lastRowmarkId);
        WRITE_BOOL_FIELD(transientPlan);
 }
 
@@ -1561,6 +1575,7 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
        WRITE_FLOAT_FIELD(tuples, "%.0f");
        WRITE_NODE_FIELD(subplan);
        WRITE_NODE_FIELD(subrtable);
+       WRITE_NODE_FIELD(subrowmark);
        WRITE_NODE_FIELD(baserestrictinfo);
        WRITE_NODE_FIELD(joininfo);
        WRITE_BOOL_FIELD(has_eclass_joins);
@@ -2001,6 +2016,7 @@ _outRowMarkClause(StringInfo str, RowMarkClause *node)
 
        WRITE_UINT_FIELD(rti);
        WRITE_UINT_FIELD(prti);
+       WRITE_UINT_FIELD(rowmarkId);
        WRITE_BOOL_FIELD(forUpdate);
        WRITE_BOOL_FIELD(noWait);
        WRITE_BOOL_FIELD(isParent);
@@ -2503,6 +2519,9 @@ _outNode(StringInfo str, void *obj)
                        case T_SetOp:
                                _outSetOp(str, obj);
                                break;
+                       case T_LockRows:
+                               _outLockRows(str, obj);
+                               break;
                        case T_Limit:
                                _outLimit(str, obj);
                                break;
index 205b6da4cd87d2a5267eaaa467557c8c3e61310c..7cffedb73b450564200af3e7935784a389e7a1a5 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.224 2009/10/08 02:39:21 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.225 2009/10/12 18:10:45 tgl Exp $
  *
  * NOTES
  *       Path and Plan nodes do not have any readfuncs support, because we
@@ -294,6 +294,7 @@ _readRowMarkClause(void)
 
        READ_UINT_FIELD(rti);
        READ_UINT_FIELD(prti);
+       READ_UINT_FIELD(rowmarkId);
        READ_BOOL_FIELD(forUpdate);
        READ_BOOL_FIELD(noWait);
        READ_BOOL_FIELD(isParent);
index 5b7f0ff0e3fa026434171f80b22aa1dcd498d328..f6fffec902e74f3b6373c2ee8c54926296277c92 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.186 2009/09/17 20:49:28 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.187 2009/10/12 18:10:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -632,6 +632,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                                                        false, tuple_fraction,
                                                                        &subroot);
        rel->subrtable = subroot->parse->rtable;
+       rel->subrowmark = subroot->parse->rowMarks;
 
        /* Copy number of output rows from subplan */
        rel->tuples = rel->subplan->plan_rows;
@@ -971,10 +972,10 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
  * since that could change the set of rows returned.
  *
  * 2. If the subquery contains any window functions, we can't push quals
- * into it, because that would change the results.
+ * into it, because that could change the results.
  *
  * 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
- * quals into it, because that would change the results.
+ * quals into it, because that could change the results.
  *
  * 4. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
  * push quals into each component query, but the quals can only reference
index b29b07659190909d73b2fabc1d8830c603ee7f01..1452bdd035cee994fbaaaa2d8bb2145725633e49 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.264 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.265 2009/10/12 18:10:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1336,7 +1336,8 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
                                                                  scan_clauses,
                                                                  scan_relid,
                                                                  best_path->parent->subplan,
-                                                                 best_path->parent->subrtable);
+                                                                 best_path->parent->subrtable,
+                                                                 best_path->parent->subrowmark);
 
        copy_path_costsize(&scan_plan->scan.plan, best_path);
 
@@ -2508,7 +2509,8 @@ make_subqueryscan(List *qptlist,
                                  List *qpqual,
                                  Index scanrelid,
                                  Plan *subplan,
-                                 List *subrtable)
+                                 List *subrtable,
+                                 List *subrowmark)
 {
        SubqueryScan *node = makeNode(SubqueryScan);
        Plan       *plan = &node->scan.plan;
@@ -2528,6 +2530,7 @@ make_subqueryscan(List *qptlist,
        node->scan.scanrelid = scanrelid;
        node->subplan = subplan;
        node->subrtable = subrtable;
+       node->subrowmark = subrowmark;
 
        return node;
 }
@@ -3590,6 +3593,31 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
        return node;
 }
 
+/*
+ * make_lockrows
+ *       Build a LockRows plan node
+ */
+LockRows *
+make_lockrows(Plan *lefttree, List *rowMarks)
+{
+       LockRows   *node = makeNode(LockRows);
+       Plan       *plan = &node->plan;
+
+       copy_plan_costsize(plan, lefttree);
+
+       /* charge cpu_tuple_cost to reflect locking costs (underestimate?) */
+       plan->total_cost += cpu_tuple_cost * plan->plan_rows;
+
+       plan->targetlist = lefttree->targetlist;
+       plan->qual = NIL;
+       plan->lefttree = lefttree;
+       plan->righttree = NULL;
+
+       node->rowMarks = rowMarks;
+
+       return node;
+}
+
 /*
  * Note: offset_est and count_est are passed in to save having to repeat
  * work already done to estimate the values of the limitOffset and limitCount
@@ -3792,6 +3820,7 @@ is_projection_capable_plan(Plan *plan)
                case T_Sort:
                case T_Unique:
                case T_SetOp:
+               case T_LockRows:
                case T_Limit:
                case T_ModifyTable:
                case T_Append:
index 4b06c823b6c4d4cebeec463da11ee51516dc54de..c17fe5f63f65b26814908c5b15691eae0064f259 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.258 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.259 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -132,7 +132,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        PlannerInfo *root;
        Plan       *top_plan;
        ListCell   *lp,
-                          *lr;
+                          *lrt,
+                          *lrm;
 
        /* Cursor options may come from caller or from DECLARE CURSOR stmt */
        if (parse->utilityStmt &&
@@ -151,11 +152,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        glob->paramlist = NIL;
        glob->subplans = NIL;
        glob->subrtables = NIL;
+       glob->subrowmarks = NIL;
        glob->rewindPlanIDs = NULL;
        glob->finalrtable = NIL;
+       glob->finalrowmarks = NIL;
        glob->relationOids = NIL;
        glob->invalItems = NIL;
        glob->lastPHId = 0;
+       glob->lastRowmarkId = 0;
        glob->transientPlan = false;
 
        /* Determine what fraction of the plan is likely to be scanned */
@@ -202,15 +206,25 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 
        /* final cleanup of the plan */
        Assert(glob->finalrtable == NIL);
-       top_plan = set_plan_references(glob, top_plan, root->parse->rtable);
+       Assert(glob->finalrowmarks == NIL);
+       top_plan = set_plan_references(glob, top_plan,
+                                                                  root->parse->rtable,
+                                                                  root->parse->rowMarks);
        /* ... and the subplans (both regular subplans and initplans) */
        Assert(list_length(glob->subplans) == list_length(glob->subrtables));
-       forboth(lp, glob->subplans, lr, glob->subrtables)
+       Assert(list_length(glob->subplans) == list_length(glob->subrowmarks));
+       lrt = list_head(glob->subrtables);
+       lrm = list_head(glob->subrowmarks);
+       foreach(lp, glob->subplans)
        {
                Plan       *subplan = (Plan *) lfirst(lp);
-               List       *subrtable = (List *) lfirst(lr);
+               List       *subrtable = (List *) lfirst(lrt);
+               List       *subrowmark = (List *) lfirst(lrm);
 
-               lfirst(lp) = set_plan_references(glob, subplan, subrtable);
+               lfirst(lp) = set_plan_references(glob, subplan,
+                                                                                subrtable, subrowmark);
+               lrt = lnext(lrt);
+               lrm = lnext(lrm);
        }
 
        /* build the PlannedStmt result */
@@ -227,7 +241,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        result->intoClause = parse->intoClause;
        result->subplans = glob->subplans;
        result->rewindPlanIDs = glob->rewindPlanIDs;
-       result->rowMarks = parse->rowMarks;
+       result->rowMarks = glob->finalrowmarks;
        result->relationOids = glob->relationOids;
        result->invalItems = glob->invalItems;
        result->nParamExec = list_length(glob->paramlist);
@@ -349,6 +363,21 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
                }
        }
 
+       /*
+        * Assign unique IDs (unique within this planner run) to RowMarkClauses.
+        * We can't identify them just by RT index because that will change
+        * during final rtable flattening, and we don't want to have to go back
+        * and change the resnames assigned to junk CTID tlist entries at that
+        * point.  Do it now before expanding inheritance sets, because child
+        * relations should inherit their parents' rowmarkId.
+        */
+       foreach(l, parse->rowMarks)
+       {
+               RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+               rc->rowmarkId = ++(root->glob->lastRowmarkId);
+       }
+
        /*
         * Expand any rangetable entries that are inheritance sets into "append
         * relations".  This can add entries to the rangetable, but they must be
@@ -1588,7 +1617,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        }
 
        /*
-        * Finally, if there is a LIMIT/OFFSET clause, add the LIMIT node.
+        * If there is a LIMIT/OFFSET clause, add the LIMIT node.
         */
        if (parse->limitCount || parse->limitOffset)
        {
@@ -1599,6 +1628,15 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                                                                                  count_est);
        }
 
+       /*
+        * Finally, if there is a FOR UPDATE/SHARE clause, add the LockRows node.
+        */
+       if (parse->rowMarks)
+       {
+               result_plan = (Plan *) make_lockrows(result_plan,
+                                                                                        parse->rowMarks);
+       }
+
        /* Compute result-relations list if needed */
        if (parse->resultRelation)
                root->resultRelations = list_make1_int(parse->resultRelation);
index 9b10b381ac7e712ce71eb335265ab366f0c09f74..20d40841ef6989a1643ade59564cbd7411ec20bf 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.151 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.152 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -166,12 +166,14 @@ static bool extract_query_dependencies_walker(Node *node,
  *     glob: global data for planner run
  *     plan: the topmost node of the plan
  *     rtable: the rangetable for the current subquery
+ *     rowmarks: the RowMarkClause list for the current subquery
  *
  * The return value is normally the same Plan node passed in, but can be
  * different when the passed-in Plan is a SubqueryScan we decide isn't needed.
  *
- * The flattened rangetable entries are appended to glob->finalrtable, and
- * plan dependencies are appended to glob->relationOids (for relations)
+ * The flattened rangetable entries are appended to glob->finalrtable,
+ * and we also append rowmarks entries to glob->finalrowmarks.
+ * Plan dependencies are appended to glob->relationOids (for relations)
  * and glob->invalItems (for everything else).
  *
  * Notice that we modify Plan nodes in-place, but use expression_tree_mutator
@@ -180,7 +182,8 @@ static bool extract_query_dependencies_walker(Node *node,
  * it's not so safe to assume that for expression tree nodes.
  */
 Plan *
-set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
+set_plan_references(PlannerGlobal *glob, Plan *plan,
+                                       List *rtable, List *rowmarks)
 {
        int                     rtoffset = list_length(glob->finalrtable);
        ListCell   *lc;
@@ -230,6 +233,26 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
                                                                                         newrte->relid);
        }
 
+       /*
+        * Adjust RT indexes of RowMarkClauses and add to final rowmarks list
+        */
+       foreach(lc, rowmarks)
+       {
+               RowMarkClause *rc = (RowMarkClause *) lfirst(lc);
+               RowMarkClause *newrc;
+
+               /* flat copy to duplicate all the scalar fields */
+               newrc = (RowMarkClause *) palloc(sizeof(RowMarkClause));
+               memcpy(newrc, rc, sizeof(RowMarkClause));
+
+               /* adjust indexes */
+               newrc->rti += rtoffset;
+               newrc->prti += rtoffset;
+               /* rowmarkId must NOT be adjusted */
+
+               glob->finalrowmarks = lappend(glob->finalrowmarks, newrc);
+       }
+
        /* Now fix the Plan tree */
        return set_plan_refs(glob, plan, rtoffset);
 }
@@ -396,6 +419,27 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
                         */
                        Assert(plan->qual == NIL);
                        break;
+               case T_LockRows:
+                       {
+                               LockRows   *splan = (LockRows *) plan;
+
+                               /*
+                                * Like the plan types above, LockRows doesn't evaluate its
+                                * tlist or quals.  But we have to fix up the RT indexes
+                                * in its rowmarks.
+                                */
+                               set_dummy_tlist_references(plan, rtoffset);
+                               Assert(splan->plan.qual == NIL);
+
+                               foreach(l, splan->rowMarks)
+                               {
+                                       RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+                                       rc->rti += rtoffset;
+                                       rc->prti += rtoffset;
+                               }
+                       }
+                       break;
                case T_Limit:
                        {
                                Limit      *splan = (Limit *) plan;
@@ -553,10 +597,12 @@ set_subqueryscan_references(PlannerGlobal *glob,
        Plan       *result;
 
        /* First, recursively process the subplan */
-       plan->subplan = set_plan_references(glob, plan->subplan, plan->subrtable);
+       plan->subplan = set_plan_references(glob, plan->subplan,
+                                                                               plan->subrtable, plan->subrowmark);
 
-       /* subrtable is no longer needed in the plan tree */
+       /* subrtable/subrowmark are no longer needed in the plan tree */
        plan->subrtable = NIL;
+       plan->subrowmark = NIL;
 
        if (trivial_subqueryscan(plan))
        {
index 6a813106d18fe7c13e6624be9fc79cd1b434be6b..7b0dd75e7fe73233d4d03c58e3fb73bffdf6b6e4 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.154 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.155 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,7 +53,8 @@ typedef struct finalize_primnode_context
 } finalize_primnode_context;
 
 
-static Node *build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
+static Node *build_subplan(PlannerInfo *root, Plan *plan,
+                         List *rtable, List *rowmarks,
                          SubLinkType subLinkType, Node *testexpr,
                          bool adjust_testexpr, bool unknownEqFalse);
 static List *generate_subquery_params(PlannerInfo *root, List *tlist,
@@ -333,7 +334,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
                                                        &subroot);
 
        /* And convert to SubPlan or InitPlan format. */
-       result = build_subplan(root, plan, subroot->parse->rtable,
+       result = build_subplan(root, plan,
+                                                  subroot->parse->rtable, subroot->parse->rowMarks,
                                                   subLinkType, testexpr, true, isTopQual);
 
        /*
@@ -375,6 +377,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
                                /* OK, convert to SubPlan format. */
                                hashplan = (SubPlan *) build_subplan(root, plan,
                                                                                                         subroot->parse->rtable,
+                                                                                                        subroot->parse->rowMarks,
                                                                                                         ANY_SUBLINK, newtestexpr,
                                                                                                         false, true);
                                /* Check we got what we expected */
@@ -402,7 +405,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType,
  * as explained in the comments for make_subplan.
  */
 static Node *
-build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
+build_subplan(PlannerInfo *root, Plan *plan, List *rtable, List *rowmarks,
                          SubLinkType subLinkType, Node *testexpr,
                          bool adjust_testexpr, bool unknownEqFalse)
 {
@@ -585,6 +588,7 @@ build_subplan(PlannerInfo *root, Plan *plan, List *rtable,
         */
        root->glob->subplans = lappend(root->glob->subplans, plan);
        root->glob->subrtables = lappend(root->glob->subrtables, rtable);
+       root->glob->subrowmarks = lappend(root->glob->subrowmarks, rowmarks);
        splan->plan_id = list_length(root->glob->subplans);
 
        if (isInitPlan)
@@ -944,6 +948,8 @@ SS_process_ctes(PlannerInfo *root)
                root->glob->subplans = lappend(root->glob->subplans, plan);
                root->glob->subrtables = lappend(root->glob->subrtables,
                                                                                 subroot->parse->rtable);
+               root->glob->subrowmarks = lappend(root->glob->subrowmarks,
+                                                                                 subroot->parse->rowMarks);
                splan->plan_id = list_length(root->glob->subplans);
 
                root->init_plans = lappend(root->init_plans, splan);
@@ -2035,6 +2041,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params)
                case T_Unique:
                case T_SetOp:
                case T_Group:
+               case T_LockRows:
                        break;
 
                default:
@@ -2191,6 +2198,8 @@ SS_make_initplan_from_plan(PlannerInfo *root, Plan *plan,
                                                                   plan);
        root->glob->subrtables = lappend(root->glob->subrtables,
                                                                         root->parse->rtable);
+       root->glob->subrowmarks = lappend(root->glob->subrowmarks,
+                                                                         root->parse->rowMarks);
 
        /*
         * Create a SubPlan node and add it to the outer list of InitPlans. Note
index 73a158e5d894fa8e0aa8b098fa7510458ae859cb..0e6010aefa9c780aa3246553c74d45a14762a480 100644 (file)
@@ -16,7 +16,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.96 2009/04/19 19:46:33 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.97 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -123,25 +123,20 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
                 */
                CheckSelectLocking(parse);
 
-               /*
-                * Currently the executor only supports FOR UPDATE/SHARE at top level
-                */
-               if (root->query_level > 1)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                       errmsg("SELECT FOR UPDATE/SHARE is not allowed in subqueries")));
-
                foreach(l, parse->rowMarks)
                {
                        RowMarkClause *rc = (RowMarkClause *) lfirst(l);
                        Var                *var;
-                       char       *resname;
+                       char            resname[32];
                        TargetEntry *tle;
 
                        /* ignore child rels */
                        if (rc->rti != rc->prti)
                                continue;
 
+                       /* we should have an ID for the RowMarkClause */
+                       Assert(rc->rowmarkId != 0);
+
                        /* always need the ctid */
                        var = makeVar(rc->rti,
                                                  SelfItemPointerAttributeNumber,
@@ -149,12 +144,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
                                                  -1,
                                                  0);
 
-                       resname = (char *) palloc(32);
-                       snprintf(resname, 32, "ctid%u", rc->rti);
+                       snprintf(resname, sizeof(resname),
+                                        "ctid%u", rc->rowmarkId);
 
                        tle = makeTargetEntry((Expr *) var,
                                                                  list_length(tlist) + 1,
-                                                                 resname,
+                                                                 pstrdup(resname),
                                                                  true);
 
                        tlist = lappend(tlist, tle);
@@ -168,12 +163,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
                                                          -1,
                                                          0);
 
-                               resname = (char *) palloc(32);
-                               snprintf(resname, 32, "tableoid%u", rc->rti);
+                               snprintf(resname, sizeof(resname),
+                                                "tableoid%u", rc->rowmarkId);
 
                                tle = makeTargetEntry((Expr *) var,
                                                                          list_length(tlist) + 1,
-                                                                         resname,
+                                                                         pstrdup(resname),
                                                                          true);
 
                                tlist = lappend(tlist, tle);
index 7e6e1fbdcbbdcca9c4f176236cc9fff84fa12896..2f43e32738b948069f4a2f8527f611bded1fc6d8 100644 (file)
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.175 2009/10/10 01:43:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.176 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -247,7 +247,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
                                                          NIL,
                                                          rtr->rtindex,
                                                          subplan,
-                                                         subroot->parse->rtable);
+                                                         subroot->parse->rtable,
+                                                         subroot->parse->rowMarks);
 
                /*
                 * We don't bother to determine the subquery's output ordering since
@@ -1281,6 +1282,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 
                        newrc->rti = childRTindex;
                        newrc->prti = rti;
+                       /* children use the same rowmarkId as their parent */
+                       newrc->rowmarkId = oldrc->rowmarkId;
                        newrc->forUpdate = oldrc->forUpdate;
                        newrc->noWait = oldrc->noWait;
                        newrc->isParent = false;
index 4ca3eeaaf28815b5a7139fad739626488445093b..58505949331c126350fce3c81dbe8b409b5a0945 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.94 2009/06/11 14:48:59 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.95 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -84,6 +84,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
        rel->tuples = 0;
        rel->subplan = NULL;
        rel->subrtable = NIL;
+       rel->subrowmark = NIL;
        rel->baserestrictinfo = NIL;
        rel->baserestrictcost.startup = 0;
        rel->baserestrictcost.per_tuple = 0;
@@ -337,6 +338,7 @@ build_join_rel(PlannerInfo *root,
        joinrel->tuples = 0;
        joinrel->subplan = NULL;
        joinrel->subrtable = NIL;
+       joinrel->subrowmark = NIL;
        joinrel->baserestrictinfo = NIL;
        joinrel->baserestrictcost.startup = 0;
        joinrel->baserestrictcost.per_tuple = 0;
index 8a19697a71ce17a8b8b51e2baf9aff87be66f78a..6aad2c79a23176a31b55c345ac46c1a36ef8fbc6 100644 (file)
@@ -17,7 +17,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.391 2009/09/09 03:32:52 tgl Exp $
+ *     $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.392 2009/10/12 18:10:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1388,7 +1388,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                                 errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
 
        /*
-        * If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT
+        * If an internal node of a set-op tree has ORDER BY, LIMIT, or FOR UPDATE
         * clauses attached, we need to treat it like a leaf node to generate an
         * independent sub-Query tree.  Otherwise, it can be represented by a
         * SetOperationStmt node underneath the parent Query.
@@ -2251,6 +2251,7 @@ applyLockingClause(Query *qry, Index rtindex, bool forUpdate, bool noWait)
        rc = makeNode(RowMarkClause);
        rc->rti = rtindex;
        rc->prti = rtindex;
+       rc->rowmarkId = 0;                      /* not used until plan time */
        rc->forUpdate = forUpdate;
        rc->noWait = noWait;
        rc->isParent = false;
index d7c2479158484dd3b8dc6fbe9edfcb4dd2efb550..abbcb006e103f6e5b1546b378709549836e62cd7 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.544 2009/10/09 21:02:56 petere Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.545 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     200910101
+#define CATALOG_VERSION_NO     200910121
 
 #endif
index cb79e26976dca34243739e1f4aca8e71a954b712..36e7d35467f0ea5df6c2647f83aca7b003baf2fa 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.161 2009/10/10 01:43:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.162 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -169,7 +169,13 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo,
 extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
                         PlanState *subplanstate,
                         ItemPointer tid, TransactionId priorXmax);
-extern PlanState *ExecGetActivePlanTree(QueryDesc *queryDesc);
+extern HeapTuple EvalPlanQualFetch(EState *estate, Index rti,
+                                 ItemPointer tid, TransactionId priorXmax);
+extern void EvalPlanQualPush(EState *estate, Index rti,
+                                                        PlanState *subplanstate);
+extern void EvalPlanQualSetTuple(EState *estate, Index rti, HeapTuple tuple);
+extern TupleTableSlot *EvalPlanQualNext(EState *estate);
+extern void EvalPlanQualPop(EState *estate, PlanState *subplanstate);
 extern DestReceiver *CreateIntoRelDestReceiver(void);
 
 /*
diff --git a/src/include/executor/nodeLockRows.h b/src/include/executor/nodeLockRows.h
new file mode 100644 (file)
index 0000000..1ad10b6
--- /dev/null
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeLockRows.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/executor/nodeLockRows.h,v 1.1 2009/10/12 18:10:51 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODELOCKROWS_H
+#define NODELOCKROWS_H
+
+#include "nodes/execnodes.h"
+
+extern LockRowsState *ExecInitLockRows(LockRows *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecLockRows(LockRowsState *node);
+extern void ExecEndLockRows(LockRowsState *node);
+extern void ExecReScanLockRows(LockRowsState *node, ExprContext *exprCtxt);
+
+#endif   /* NODELOCKROWS_H */
index 264ef741daf544f8a66b7c8f651369d89d34f701..93d4aa00db36b9822ea0b3b066345724a6b2e0c2 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.209 2009/10/10 01:43:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.210 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -332,6 +332,8 @@ typedef struct EState
        Snapshot        es_crosscheck_snapshot; /* crosscheck time qual for RI */
        List       *es_range_table; /* List of RangeTblEntry */
 
+       JunkFilter *es_junkFilter;      /* top-level junk filter, if any */
+
        /* If query can insert/delete tuples, the command ID to mark them with */
        CommandId       es_output_cid;
 
@@ -339,7 +341,6 @@ typedef struct EState
        ResultRelInfo *es_result_relations; /* array of ResultRelInfos */
        int                     es_num_result_relations;                /* length of array */
        ResultRelInfo *es_result_relation_info;         /* currently active array elt */
-       JunkFilter *es_junkFilter;      /* top-level junk filter, if any */
 
        /* Stuff used for firing triggers: */
        List       *es_trig_target_relations;           /* trigger-only ResultRelInfos */
@@ -354,9 +355,10 @@ typedef struct EState
 
        List       *es_tupleTable;      /* List of TupleTableSlots */
 
+       List       *es_rowMarks;        /* List of ExecRowMarks */
+
        uint32          es_processed;   /* # of tuples processed */
        Oid                     es_lastoid;             /* last oid processed (by INSERT) */
-       List       *es_rowMarks;        /* not good place, but there is no other */
 
        bool            es_instrument;  /* true requests runtime instrumentation */
        bool            es_select_into; /* true if doing SELECT INTO */
@@ -378,19 +380,22 @@ typedef struct EState
        struct evalPlanQual *es_evalPlanQual;           /* chain of PlanQual states */
        bool       *es_evTupleNull; /* local array of EPQ status */
        HeapTuple  *es_evTuple;         /* shared array of EPQ substitute tuples */
-       bool            es_useEvalPlan; /* evaluating EPQ tuples? */
 } EState;
 
 
 /*
- * es_rowMarks is a list of these structs.     See RowMarkClause for details
- * about rti and prti. toidAttno is not used in a "plain" rowmark.
+ * es_rowMarks is a list of these structs.  Each LockRows node has its own
+ * list, which is the subset of locks that it is supposed to enforce; note
+ * that the per-node lists point to the same structs that are in the global
+ * list.  See RowMarkClause for details about rti, prti, and rowmarkId.
+ * toidAttno is not used in a "plain" (non-inherited) rowmark.
  */
 typedef struct ExecRowMark
 {
        Relation        relation;               /* opened and RowShareLock'd relation */
        Index           rti;                    /* its range table index */
        Index           prti;                   /* parent range table index, if child */
+       Index           rowmarkId;              /* unique identifier assigned by planner */
        bool            forUpdate;              /* true = FOR UPDATE, false = FOR SHARE */
        bool            noWait;                 /* NOWAIT option */
        AttrNumber      ctidAttNo;              /* resno of its ctid junk attribute */
@@ -1636,6 +1641,20 @@ typedef struct SetOpState
        TupleHashIterator hashiter; /* for iterating through hash table */
 } SetOpState;
 
+/* ----------------
+ *      LockRowsState information
+ *
+ *             LockRows nodes are used to enforce FOR UPDATE/FOR SHARE locking.
+ * ----------------
+ */
+typedef struct LockRowsState
+{
+       PlanState       ps;                             /* its first field is NodeTag */
+       List       *lr_rowMarks;        /* List of ExecRowMarks */
+       JunkFilter *lr_junkFilter;      /* needed for getting ctid columns */
+       bool            lr_useEvalPlan; /* evaluating EPQ tuples? */
+} LockRowsState;
+
 /* ----------------
  *      LimitState information
  *
index 53c406cc518f0e6e1efe879efad7b1cbbc873187..c452d53505ab5cbf29dbd6b306fe18ed3454b8d6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.229 2009/10/10 01:43:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.230 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,6 +71,7 @@ typedef enum NodeTag
        T_Unique,
        T_Hash,
        T_SetOp,
+       T_LockRows,
        T_Limit,
        /* this one isn't a subclass of Plan: */
        T_PlanInvalItem,
@@ -110,6 +111,7 @@ typedef enum NodeTag
        T_UniqueState,
        T_HashState,
        T_SetOpState,
+       T_LockRowsState,
        T_LimitState,
 
        /*
index 5f0d763b688738278051bc76cee542b37658e966..757aebef1a442de6d0bbdc22f5bf07bf13b0bee2 100644 (file)
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.405 2009/10/07 22:14:26 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.406 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -809,12 +809,15 @@ typedef struct WindowClause
  * the target rel itself in its role as a child).  The child entries have
  * rti == child rel's RT index, prti == parent's RT index, and can therefore
  * be recognized as children by the fact that prti != rti.
+ * rowmarkId is a unique ID for the RowMarkClause across an entire query,
+ * and is assigned during planning; it's always zero upstream of the planner.
  */
 typedef struct RowMarkClause
 {
        NodeTag         type;
        Index           rti;                    /* range table index of target relation */
        Index           prti;                   /* range table index of parent relation */
+       Index           rowmarkId;              /* unique identifier assigned by planner */
        bool            forUpdate;              /* true = FOR UPDATE, false = FOR SHARE */
        bool            noWait;                 /* NOWAIT option */
        bool            isParent;               /* set by planner when expanding inheritance */
index 26b0fc333568ff073b4b02c01810584729d022e7..10177f3d7fc879a51fd7d87e072ad5e54369a621 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.111 2009/10/10 01:43:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.112 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -343,7 +343,7 @@ typedef struct TidScan
  *
  * Note: subrtable is used just to carry the subquery rangetable from
  * createplan.c to setrefs.c; it should always be NIL by the time the
- * executor sees the plan.
+ * executor sees the plan.  Similarly for subrowmark.
  * ----------------
  */
 typedef struct SubqueryScan
@@ -351,6 +351,7 @@ typedef struct SubqueryScan
        Scan            scan;
        Plan       *subplan;
        List       *subrtable;          /* temporary workspace for planner */
+       List       *subrowmark;         /* temporary workspace for planner */
 } SubqueryScan;
 
 /* ----------------
@@ -614,6 +615,19 @@ typedef struct SetOp
        long            numGroups;              /* estimated number of groups in input */
 } SetOp;
 
+/* ----------------
+ *             lock-rows node
+ *
+ * rowMarks identifies the rels to be locked by this node; it should be
+ * a subset of the rowMarks listed in the top-level PlannedStmt.
+ * ----------------
+ */
+typedef struct LockRows
+{
+       Plan            plan;
+       List       *rowMarks;           /* a list of RowMarkClause's */
+} LockRows;
+
 /* ----------------
  *             limit node
  *
index 5e504b0ab4ba5a03faa54362f7bd2c389dae23c8..8edb58727c09c780780285c9ef128a61050bce8d 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.176 2009/10/10 01:43:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.177 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,16 +68,22 @@ typedef struct PlannerGlobal
 
        List       *subrtables;         /* Rangetables for SubPlan nodes */
 
+       List       *subrowmarks;        /* RowMarkClauses for SubPlan nodes */
+
        Bitmapset  *rewindPlanIDs;      /* indices of subplans that require REWIND */
 
        List       *finalrtable;        /* "flat" rangetable for executor */
 
+       List       *finalrowmarks;      /* "flat" list of RowMarkClauses */
+
        List       *relationOids;       /* OIDs of relations the plan depends on */
 
        List       *invalItems;         /* other dependencies, as PlanInvalItems */
 
        Index           lastPHId;               /* highest PlaceHolderVar ID assigned */
 
+       Index           lastRowmarkId;  /* highest RowMarkClause ID assigned */
+
        bool            transientPlan;  /* redo plan when TransactionXmin changes? */
 } PlannerGlobal;
 
@@ -281,6 +287,7 @@ typedef struct PlannerInfo
  *             tuples - number of tuples in relation (not considering restrictions)
  *             subplan - plan for subquery (NULL if it's not a subquery)
  *             subrtable - rangetable for subquery (NIL if it's not a subquery)
+ *             subrowmark - rowmarks for subquery (NIL if it's not a subquery)
  *
  *             Note: for a subquery, tuples and subplan are not set immediately
  *             upon creation of the RelOptInfo object; they are filled in when
@@ -364,6 +371,7 @@ typedef struct RelOptInfo
        double          tuples;
        struct Plan *subplan;           /* if subquery */
        List       *subrtable;          /* if subquery */
+       List       *subrowmark;         /* if subquery */
 
        /* used by various scans and joins: */
        List       *baserestrictinfo;           /* RestrictInfo structures (if base
index 1e27bd847ce73cc119d7893466d06e945a423cb3..acf24bc17ed84583f3af613ca4ff882a444eb166 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.119 2009/10/10 01:43:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.120 2009/10/12 18:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,7 +40,8 @@ extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
  */
 extern Plan *create_plan(PlannerInfo *root, Path *best_path);
 extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
-                                 Index scanrelid, Plan *subplan, List *subrtable);
+                                 Index scanrelid, Plan *subplan,
+                                 List *subrtable, List *subrowmark);
 extern Append *make_append(List *appendplans, List *tlist);
 extern RecursiveUnion *make_recursive_union(List *tlist,
                                         Plan *lefttree, Plan *righttree, int wtParam,
@@ -67,6 +68,7 @@ extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
                   Plan *lefttree);
 extern Plan *materialize_finished_plan(Plan *subplan);
 extern Unique *make_unique(Plan *lefttree, List *distinctList);
+extern LockRows *make_lockrows(Plan *lefttree, List *rowMarks);
 extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
                   int64 offset_est, int64 count_est);
 extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
@@ -108,7 +110,8 @@ extern RestrictInfo *build_implied_join_equality(Oid opno,
  */
 extern Plan *set_plan_references(PlannerGlobal *glob,
                                        Plan *plan,
-                                       List *rtable);
+                                       List *rtable,
+                                       List *rowmarks);
 extern List *set_returning_clause_references(PlannerGlobal *glob,
                                                                List *rlist,
                                                                Plan *topplan,