summaryrefslogtreecommitdiff
path: root/src/backend/executor/execMain.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2009-10-10 01:43:50 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2009-10-10 01:43:50 +0000
commit8a5849b7ff24c637a1140c26fc171e45c9142005 (patch)
tree8f660c08709c999c3a4299436312390c53231b01 /src/backend/executor/execMain.c
parentb865d2758255b767e30dc5f23c7c7d209e716f3b (diff)
downloadpostgresql-8a5849b7ff24c637a1140c26fc171e45c9142005.tar.gz
Split the processing of INSERT/UPDATE/DELETE operations out of execMain.c.
They are now handled by a new plan node type called ModifyTable, which is placed at the top of the plan tree. In itself this change doesn't do much, except perhaps make the handling of RETURNING lists and inherited UPDATEs a tad less klugy. But it is necessary preparation for the intended extension of allowing RETURNING queries inside WITH. Marko Tiikkaja
Diffstat (limited to 'src/backend/executor/execMain.c')
-rw-r--r--src/backend/executor/execMain.c949
1 files changed, 94 insertions, 855 deletions
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 82b05dc4d1..7c788ea6df 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,13 +26,12 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.331 2009/10/08 22:34:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.332 2009/10/10 01:43:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/transam.h"
@@ -44,17 +43,13 @@
#include "commands/trigger.h"
#include "executor/execdebug.h"
#include "executor/instrument.h"
-#include "executor/nodeSubplan.h"
#include "miscadmin.h"
-#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
-#include "storage/smgr.h"
#include "utils/acl.h"
-#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@@ -77,35 +72,20 @@ typedef struct evalPlanQual
/* decls for local routines only used within this module */
static void InitPlan(QueryDesc *queryDesc, int eflags);
-static void ExecCheckPlanOutput(Relation resultRel, List *targetList);
static void ExecEndPlan(PlanState *planstate, EState *estate);
static void ExecutePlan(EState *estate, PlanState *planstate,
CmdType operation,
+ bool sendTuples,
long numberTuples,
ScanDirection direction,
DestReceiver *dest);
-static void ExecSelect(TupleTableSlot *slot,
- DestReceiver *dest, EState *estate);
-static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid,
- TupleTableSlot *planSlot,
- DestReceiver *dest, EState *estate);
-static void ExecDelete(ItemPointer tupleid,
- TupleTableSlot *planSlot,
- DestReceiver *dest, EState *estate);
-static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid,
- TupleTableSlot *planSlot,
- DestReceiver *dest, EState *estate);
-static void ExecProcessReturning(ProjectionInfo *projectReturning,
- TupleTableSlot *tupleSlot,
- TupleTableSlot *planSlot,
- DestReceiver *dest);
static TupleTableSlot *EvalPlanQualNext(EState *estate);
static void EndEvalPlanQual(EState *estate);
static void ExecCheckRTPerms(List *rangeTable);
static void ExecCheckRTEPerms(RangeTblEntry *rte);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
static void EvalPlanQualStart(evalPlanQual *epq, EState *estate,
- evalPlanQual *priorepq);
+ Plan *planTree, evalPlanQual *priorepq);
static void EvalPlanQualStop(evalPlanQual *epq);
static void OpenIntoRel(QueryDesc *queryDesc);
static void CloseIntoRel(QueryDesc *queryDesc);
@@ -297,7 +277,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
estate->es_lastoid = InvalidOid;
sendTuples = (operation == CMD_SELECT ||
- queryDesc->plannedstmt->returningLists);
+ queryDesc->plannedstmt->hasReturning);
if (sendTuples)
(*dest->rStartup) (dest, operation, queryDesc->tupDesc);
@@ -309,6 +289,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
ExecutePlan(estate,
queryDesc->planstate,
operation,
+ sendTuples,
count,
direction,
dest);
@@ -668,7 +649,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_range_table = rangeTable;
/*
- * initialize result relation stuff
+ * initialize result relation stuff, and open/lock the result rels.
+ *
+ * We must do this before initializing the plan tree, else we might
+ * try to do a lock upgrade if a result rel is also a source rel.
*/
if (plannedstmt->resultRelations)
{
@@ -697,8 +681,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
}
estate->es_result_relations = resultRelInfos;
estate->es_num_result_relations = numResultRelations;
- /* Initialize to first or only result rel */
- estate->es_result_relation_info = resultRelInfos;
+ /* es_result_relation_info is NULL except when within ModifyTable */
+ estate->es_result_relation_info = NULL;
}
else
{
@@ -755,12 +739,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
}
/*
- * Initialize the executor's tuple table. Also, if it's not a SELECT,
- * set up a tuple table slot for use for trigger output tuples.
+ * Initialize the executor's tuple table to empty.
*/
estate->es_tupleTable = NIL;
- if (operation != CMD_SELECT)
- estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+ estate->es_trig_tuple_slot = NULL;
/* mark EvalPlanQual not active */
estate->es_plannedstmt = plannedstmt;
@@ -814,214 +796,70 @@ InitPlan(QueryDesc *queryDesc, int eflags)
tupType = ExecGetResultType(planstate);
/*
- * Initialize the junk filter if needed. SELECT and INSERT queries need a
- * filter if there are any junk attrs in the tlist. UPDATE and DELETE
- * always need a filter, since there's always a junk 'ctid' attribute
- * present --- no need to look first.
- *
- * This section of code is also a convenient place to verify that the
- * output of an INSERT or UPDATE matches the target table(s).
+ * Initialize the junk filter if needed. SELECT queries need a
+ * filter if there are any junk attrs in the top-level tlist.
*/
+ if (operation == CMD_SELECT)
{
bool junk_filter_needed = false;
ListCell *tlist;
- switch (operation)
+ foreach(tlist, plan->targetlist)
{
- case CMD_SELECT:
- case CMD_INSERT:
- foreach(tlist, plan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(tlist);
+ TargetEntry *tle = (TargetEntry *) lfirst(tlist);
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
+ if (tle->resjunk)
+ {
junk_filter_needed = true;
break;
- default:
- break;
+ }
}
if (junk_filter_needed)
{
- /*
- * If there are multiple result relations, each one needs its own
- * junk filter. Note this is only possible for UPDATE/DELETE, so
- * we can't be fooled by some needing a filter and some not.
- */
- if (list_length(plannedstmt->resultRelations) > 1)
- {
- PlanState **appendplans;
- int as_nplans;
- ResultRelInfo *resultRelInfo;
-
- /* Top plan had better be an Append here. */
- Assert(IsA(plan, Append));
- Assert(((Append *) plan)->isTarget);
- Assert(IsA(planstate, AppendState));
- appendplans = ((AppendState *) planstate)->appendplans;
- as_nplans = ((AppendState *) planstate)->as_nplans;
- Assert(as_nplans == estate->es_num_result_relations);
- resultRelInfo = estate->es_result_relations;
- for (i = 0; i < as_nplans; i++)
- {
- PlanState *subplan = appendplans[i];
- JunkFilter *j;
-
- if (operation == CMD_UPDATE)
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->plan->targetlist);
-
- j = ExecInitJunkFilter(subplan->plan->targetlist,
- resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
- ExecInitExtraTupleSlot(estate));
-
- /*
- * Since it must be UPDATE/DELETE, there had better be a
- * "ctid" junk attribute in the tlist ... but ctid could
- * be at a different resno for each result relation. We
- * look up the ctid resnos now and save them in the
- * junkfilters.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
- }
-
- /*
- * Set active junkfilter too; at this point ExecInitAppend has
- * already selected an active result relation...
- */
- estate->es_junkFilter =
- estate->es_result_relation_info->ri_junkFilter;
-
- /*
- * We currently can't support rowmarks in this case, because
- * the associated junk CTIDs might have different resnos in
- * different subplans.
- */
- if (estate->es_rowMarks)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SELECT FOR UPDATE/SHARE is not supported within a query with multiple result relations")));
- }
- else
- {
- /* Normal case with just one JunkFilter */
- JunkFilter *j;
-
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
- planstate->plan->targetlist);
+ JunkFilter *j;
- j = ExecInitJunkFilter(planstate->plan->targetlist,
- tupType->tdhasoid,
- ExecInitExtraTupleSlot(estate));
- estate->es_junkFilter = j;
- if (estate->es_result_relation_info)
- estate->es_result_relation_info->ri_junkFilter = j;
+ j = ExecInitJunkFilter(planstate->plan->targetlist,
+ tupType->tdhasoid,
+ ExecInitExtraTupleSlot(estate));
+ estate->es_junkFilter = j;
- if (operation == CMD_SELECT)
- {
- /* For SELECT, want to return the cleaned tuple type */
- tupType = j->jf_cleanTupType;
- }
- else if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the ctid junk attr now */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
+ /* 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)
+ /* 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)
{
- ExecRowMark *erm = (ExecRowMark *) lfirst(l);
- char resname[32];
-
- /* always need the ctid */
- snprintf(resname, sizeof(resname), "ctid%u",
+ snprintf(resname, sizeof(resname), "tableoid%u",
erm->prti);
- erm->ctidAttNo = ExecFindJunkAttribute(j, resname);
- if (!AttributeNumberIsValid(erm->ctidAttNo))
+ erm->toidAttNo = ExecFindJunkAttribute(j, resname);
+ if (!AttributeNumberIsValid(erm->toidAttNo))
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
{
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(estate->es_result_relation_info->ri_RelationDesc,
- planstate->plan->targetlist);
-
estate->es_junkFilter = NULL;
if (estate->es_rowMarks)
elog(ERROR, "SELECT FOR UPDATE/SHARE, but no junk columns");
}
}
- /*
- * Initialize RETURNING projections if needed.
- */
- if (plannedstmt->returningLists)
- {
- TupleTableSlot *slot;
- ExprContext *econtext;
- ResultRelInfo *resultRelInfo;
-
- /*
- * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case.
- * We assume all the sublists will generate the same output tupdesc.
- */
- tupType = ExecTypeFromTL((List *) linitial(plannedstmt->returningLists),
- false);
-
- /* Set up a slot for the output of the RETURNING projection(s) */
- slot = ExecInitExtraTupleSlot(estate);
- ExecSetSlotDescriptor(slot, tupType);
- /* Need an econtext too */
- econtext = CreateExprContext(estate);
-
- /*
- * Build a projection for each result rel. Note that any SubPlans in
- * the RETURNING lists get attached to the topmost plan node.
- */
- Assert(list_length(plannedstmt->returningLists) == estate->es_num_result_relations);
- resultRelInfo = estate->es_result_relations;
- foreach(l, plannedstmt->returningLists)
- {
- List *rlist = (List *) lfirst(l);
- List *rliststate;
-
- rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate);
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rliststate, econtext, slot,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
- }
-
queryDesc->tupDesc = tupType;
queryDesc->planstate = planstate;
@@ -1123,75 +961,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
}
/*
- * Verify that the tuples to be produced by INSERT or UPDATE match the
- * target relation's rowtype
- *
- * We do this to guard against stale plans. If plan invalidation is
- * functioning properly then we should never get a failure here, but better
- * safe than sorry. Note that this is called after we have obtained lock
- * on the target rel, so the rowtype can't change underneath us.
- *
- * The plan output is represented by its targetlist, because that makes
- * handling the dropped-column case easier.
- */
-static void
-ExecCheckPlanOutput(Relation resultRel, List *targetList)
-{
- TupleDesc resultDesc = RelationGetDescr(resultRel);
- int attno = 0;
- ListCell *lc;
-
- foreach(lc, targetList)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(lc);
- Form_pg_attribute attr;
-
- if (tle->resjunk)
- continue; /* ignore junk tlist items */
-
- if (attno >= resultDesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Query has too many columns.")));
- attr = resultDesc->attrs[attno++];
-
- if (!attr->attisdropped)
- {
- /* Normal case: demand type match */
- if (exprType((Node *) tle->expr) != attr->atttypid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Table has type %s at ordinal position %d, but query expects %s.",
- format_type_be(attr->atttypid),
- attno,
- format_type_be(exprType((Node *) tle->expr)))));
- }
- else
- {
- /*
- * For a dropped column, we can't check atttypid (it's likely 0).
- * In any case the planner has most likely inserted an INT4 null.
- * What we insist on is just *some* NULL constant.
- */
- if (!IsA(tle->expr, Const) ||
- !((Const *) tle->expr)->constisnull)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Query provides a value for a dropped column at ordinal position %d.",
- attno)));
- }
- }
- if (attno != resultDesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Query has too few columns.")));
-}
-
-/*
* ExecGetTriggerResultRel
*
* Get a ResultRelInfo for a trigger target relation. Most of the time,
@@ -1281,11 +1050,13 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
* recognize how far down the requirement really goes, but for now we just
* make all plan nodes do the same thing if the top level forces the choice.
*
- * We assume that estate->es_result_relation_info is already set up to
- * describe the target relation. Note that in an UPDATE that spans an
- * inheritance tree, some of the target relations may have OIDs and some not.
- * We have to make the decisions on a per-relation basis as we initialize
- * each of the child plans of the topmost Append plan.
+ * We assume that if we are generating tuples for INSERT or UPDATE,
+ * estate->es_result_relation_info is already set up to describe the target
+ * relation. Note that in an UPDATE that spans an inheritance tree, some of
+ * the target relations may have OIDs and some not. We have to make the
+ * decisions on a per-relation basis as we initialize each of the subplans of
+ * the ModifyTable node, so ModifyTable has to set es_result_relation_info
+ * while initializing each subplan.
*
* SELECT INTO is even uglier, because we don't have the INTO relation's
* descriptor available when this code runs; we have to look aside at a
@@ -1294,27 +1065,25 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
bool
ExecContextForcesOids(PlanState *planstate, bool *hasoids)
{
- if (planstate->state->es_select_into)
- {
- *hasoids = planstate->state->es_into_oids;
- return true;
- }
- else
+ ResultRelInfo *ri = planstate->state->es_result_relation_info;
+
+ if (ri != NULL)
{
- ResultRelInfo *ri = planstate->state->es_result_relation_info;
+ Relation rel = ri->ri_RelationDesc;
- if (ri != NULL)
+ if (rel != NULL)
{
- Relation rel = ri->ri_RelationDesc;
-
- if (rel != NULL)
- {
- *hasoids = rel->rd_rel->relhasoids;
- return true;
- }
+ *hasoids = rel->rd_rel->relhasoids;
+ return true;
}
}
+ if (planstate->state->es_select_into)
+ {
+ *hasoids = planstate->state->es_into_oids;
+ return true;
+ }
+
return false;
}
@@ -1416,6 +1185,7 @@ static void
ExecutePlan(EState *estate,
PlanState *planstate,
CmdType operation,
+ bool sendTuples,
long numberTuples,
ScanDirection direction,
DestReceiver *dest)
@@ -1423,8 +1193,6 @@ ExecutePlan(EState *estate,
JunkFilter *junkfilter;
TupleTableSlot *planSlot;
TupleTableSlot *slot;
- ItemPointer tupleid = NULL;
- ItemPointerData tuple_ctid;
long current_tuple_count;
/*
@@ -1438,25 +1206,6 @@ ExecutePlan(EState *estate,
estate->es_direction = direction;
/*
- * Process BEFORE EACH STATEMENT triggers
- */
- switch (operation)
- {
- case CMD_UPDATE:
- ExecBSUpdateTriggers(estate, estate->es_result_relation_info);
- break;
- case CMD_DELETE:
- ExecBSDeleteTriggers(estate, estate->es_result_relation_info);
- break;
- case CMD_INSERT:
- ExecBSInsertTriggers(estate, estate->es_result_relation_info);
- break;
- default:
- /* do nothing */
- break;
- }
-
- /*
* Loop until we've processed the proper number of tuples from the plan.
*/
for (;;)
@@ -1578,6 +1327,7 @@ lnext: ;
/* updated, so look at updated version */
newSlot = EvalPlanQual(estate,
erm->rti,
+ planstate,
&update_ctid,
update_xmax);
if (!TupIsNull(newSlot))
@@ -1605,61 +1355,25 @@ lnext: ;
}
/*
- * extract the 'ctid' junk attribute.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- Datum datum;
- bool isNull;
-
- datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "ctid is NULL");
-
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* make sure we don't free the ctid!! */
- tupleid = &tuple_ctid;
- }
-
- /*
- * Create a new "clean" tuple with all junk attributes removed. We
- * don't need to do this for DELETE, however (there will in fact
- * be no non-junk attributes in a DELETE!)
+ * Create a new "clean" tuple with all junk attributes removed.
*/
- if (operation != CMD_DELETE)
- slot = ExecFilterJunk(junkfilter, slot);
+ slot = ExecFilterJunk(junkfilter, slot);
}
/*
- * now that we have a tuple, do the appropriate thing with it.. either
- * send it to the output destination, add it to a relation someplace,
- * delete it from a relation, or modify some of its attributes.
+ * If we are supposed to send the tuple somewhere, do so.
+ * (In practice this is probably always the case at this point.)
*/
- switch (operation)
- {
- case CMD_SELECT:
- ExecSelect(slot, dest, estate);
- break;
+ if (sendTuples)
+ (*dest->receiveSlot) (slot, dest);
- case CMD_INSERT:
- ExecInsert(slot, tupleid, planSlot, dest, estate);
- break;
-
- case CMD_DELETE:
- ExecDelete(tupleid, planSlot, dest, estate);
- break;
-
- case CMD_UPDATE:
- ExecUpdate(slot, tupleid, planSlot, dest, estate);
- break;
-
- default:
- elog(ERROR, "unrecognized operation code: %d",
- (int) operation);
- break;
- }
+ /*
+ * Count tuples processed, if this is a SELECT. (For other operation
+ * types, the ModifyTable plan node must count the appropriate
+ * events.)
+ */
+ if (operation == CMD_SELECT)
+ (estate->es_processed)++;
/*
* check our tuple count.. if we've processed the proper number then
@@ -1670,453 +1384,8 @@ lnext: ;
if (numberTuples && numberTuples == current_tuple_count)
break;
}
-
- /*
- * Process AFTER EACH STATEMENT triggers
- */
- switch (operation)
- {
- case CMD_UPDATE:
- ExecASUpdateTriggers(estate, estate->es_result_relation_info);
- break;
- case CMD_DELETE:
- ExecASDeleteTriggers(estate, estate->es_result_relation_info);
- break;
- case CMD_INSERT:
- ExecASInsertTriggers(estate, estate->es_result_relation_info);
- break;
- default:
- /* do nothing */
- break;
- }
-}
-
-/* ----------------------------------------------------------------
- * ExecSelect
- *
- * SELECTs are easy.. we just pass the tuple to the appropriate
- * output function.
- * ----------------------------------------------------------------
- */
-static void
-ExecSelect(TupleTableSlot *slot,
- DestReceiver *dest,
- EState *estate)
-{
- (*dest->receiveSlot) (slot, dest);
- (estate->es_processed)++;
-}
-
-/* ----------------------------------------------------------------
- * ExecInsert
- *
- * INSERTs are trickier.. we have to insert the tuple into
- * the base relation and insert appropriate tuples into the
- * index relations.
- * ----------------------------------------------------------------
- */
-static void
-ExecInsert(TupleTableSlot *slot,
- ItemPointer tupleid,
- TupleTableSlot *planSlot,
- DestReceiver *dest,
- EState *estate)
-{
- HeapTuple tuple;
- ResultRelInfo *resultRelInfo;
- Relation resultRelationDesc;
- Oid newId;
- List *recheckIndexes = NIL;
-
- /*
- * get the heap tuple out of the tuple table slot, making sure we have a
- * writable copy
- */
- tuple = ExecMaterializeSlot(slot);
-
- /*
- * get information on the (current) result relation
- */
- resultRelInfo = estate->es_result_relation_info;
- resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
- /*
- * If the result relation has OIDs, force the tuple's OID to zero so that
- * heap_insert will assign a fresh OID. Usually the OID already will be
- * zero at this point, but there are corner cases where the plan tree can
- * return a tuple extracted literally from some table with the same
- * rowtype.
- *
- * XXX if we ever wanted to allow users to assign their own OIDs to new
- * rows, this'd be the place to do it. For the moment, we make a point of
- * doing this before calling triggers, so that a user-supplied trigger
- * could hack the OID if desired.
- */
- if (resultRelationDesc->rd_rel->relhasoids)
- HeapTupleSetOid(tuple, InvalidOid);
-
- /* BEFORE ROW INSERT Triggers */
- if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
- {
- HeapTuple newtuple;
-
- newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
-
- if (newtuple == NULL) /* "do nothing" */
- return;
-
- if (newtuple != tuple) /* modified by Trigger(s) */
- {
- /*
- * Put the modified tuple into a slot for convenience of routines
- * below. We assume the tuple was allocated in per-tuple memory
- * context, and therefore will go away by itself. The tuple table
- * slot should not try to clear it.
- */
- TupleTableSlot *newslot = estate->es_trig_tuple_slot;
-
- if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
- ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
- ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
- slot = newslot;
- tuple = newtuple;
- }
- }
-
- /*
- * Check the constraints of the tuple
- */
- if (resultRelationDesc->rd_att->constr)
- ExecConstraints(resultRelInfo, slot, estate);
-
- /*
- * insert the tuple
- *
- * Note: heap_insert returns the tid (location) of the new tuple in the
- * t_self field.
- */
- newId = heap_insert(resultRelationDesc, tuple,
- estate->es_output_cid, 0, NULL);
-
- (estate->es_processed)++;
- estate->es_lastoid = newId;
- setLastTid(&(tuple->t_self));
-
- /*
- * insert index entries for tuple
- */
- if (resultRelInfo->ri_NumIndices > 0)
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
- estate, false);
-
- /* AFTER ROW INSERT Triggers */
- ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
-
- /* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- ExecProcessReturning(resultRelInfo->ri_projectReturning,
- slot, planSlot, dest);
-}
-
-/* ----------------------------------------------------------------
- * ExecDelete
- *
- * DELETE is like UPDATE, except that we delete the tuple and no
- * index modifications are needed
- * ----------------------------------------------------------------
- */
-static void
-ExecDelete(ItemPointer tupleid,
- TupleTableSlot *planSlot,
- DestReceiver *dest,
- EState *estate)
-{
- ResultRelInfo *resultRelInfo;
- Relation resultRelationDesc;
- HTSU_Result result;
- ItemPointerData update_ctid;
- TransactionId update_xmax;
-
- /*
- * get information on the (current) result relation
- */
- resultRelInfo = estate->es_result_relation_info;
- resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
- /* BEFORE ROW DELETE Triggers */
- if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
- {
- bool dodelete;
-
- dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid);
-
- if (!dodelete) /* "do nothing" */
- return;
- }
-
- /*
- * delete the tuple
- *
- * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
- * the row to be deleted is visible to that snapshot, and throw a can't-
- * serialize error if not. This is a special-case behavior needed for
- * referential integrity updates in serializable transactions.
- */
-ldelete:;
- result = heap_delete(resultRelationDesc, tupleid,
- &update_ctid, &update_xmax,
- estate->es_output_cid,
- estate->es_crosscheck_snapshot,
- true /* wait for commit */ );
- switch (result)
- {
- case HeapTupleSelfUpdated:
- /* already deleted by self; nothing to do */
- return;
-
- case HeapTupleMayBeUpdated:
- break;
-
- case HeapTupleUpdated:
- if (IsXactIsoLevelSerializable)
- ereport(ERROR,
- (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
- errmsg("could not serialize access due to concurrent update")));
- else if (!ItemPointerEquals(tupleid, &update_ctid))
- {
- TupleTableSlot *epqslot;
-
- epqslot = EvalPlanQual(estate,
- resultRelInfo->ri_RangeTableIndex,
- &update_ctid,
- update_xmax);
- if (!TupIsNull(epqslot))
- {
- *tupleid = update_ctid;
- goto ldelete;
- }
- }
- /* tuple already deleted; nothing to do */
- return;
-
- default:
- elog(ERROR, "unrecognized heap_delete status: %u", result);
- return;
- }
-
- (estate->es_processed)++;
-
- /*
- * Note: Normally one would think that we have to delete index tuples
- * associated with the heap tuple now...
- *
- * ... but in POSTGRES, we have no need to do this because VACUUM will
- * take care of it later. We can't delete index tuples immediately
- * anyway, since the tuple is still visible to other transactions.
- */
-
- /* AFTER ROW DELETE Triggers */
- ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
-
- /* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- {
- /*
- * We have to put the target tuple into a slot, which means first we
- * gotta fetch it. We can use the trigger tuple slot.
- */
- TupleTableSlot *slot = estate->es_trig_tuple_slot;
- HeapTupleData deltuple;
- Buffer delbuffer;
-
- deltuple.t_self = *tupleid;
- if (!heap_fetch(resultRelationDesc, SnapshotAny,
- &deltuple, &delbuffer, false, NULL))
- elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
-
- if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
- ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
- ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
-
- ExecProcessReturning(resultRelInfo->ri_projectReturning,
- slot, planSlot, dest);
-
- ExecClearTuple(slot);
- ReleaseBuffer(delbuffer);
- }
}
-/* ----------------------------------------------------------------
- * ExecUpdate
- *
- * note: we can't run UPDATE queries with transactions
- * off because UPDATEs are actually INSERTs and our
- * scan will mistakenly loop forever, updating the tuple
- * it just inserted.. This should be fixed but until it
- * is, we don't want to get stuck in an infinite loop
- * which corrupts your database..
- * ----------------------------------------------------------------
- */
-static void
-ExecUpdate(TupleTableSlot *slot,
- ItemPointer tupleid,
- TupleTableSlot *planSlot,
- DestReceiver *dest,
- EState *estate)
-{
- HeapTuple tuple;
- ResultRelInfo *resultRelInfo;
- Relation resultRelationDesc;
- HTSU_Result result;
- ItemPointerData update_ctid;
- TransactionId update_xmax;
- List *recheckIndexes = NIL;
-
- /*
- * abort the operation if not running transactions
- */
- if (IsBootstrapProcessingMode())
- elog(ERROR, "cannot UPDATE during bootstrap");
-
- /*
- * get the heap tuple out of the tuple table slot, making sure we have a
- * writable copy
- */
- tuple = ExecMaterializeSlot(slot);
-
- /*
- * get information on the (current) result relation
- */
- resultRelInfo = estate->es_result_relation_info;
- resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
- /* BEFORE ROW UPDATE Triggers */
- if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
- {
- HeapTuple newtuple;
-
- newtuple = ExecBRUpdateTriggers(estate, resultRelInfo,
- tupleid, tuple);
-
- if (newtuple == NULL) /* "do nothing" */
- return;
-
- if (newtuple != tuple) /* modified by Trigger(s) */
- {
- /*
- * Put the modified tuple into a slot for convenience of routines
- * below. We assume the tuple was allocated in per-tuple memory
- * context, and therefore will go away by itself. The tuple table
- * slot should not try to clear it.
- */
- TupleTableSlot *newslot = estate->es_trig_tuple_slot;
-
- if (newslot->tts_tupleDescriptor != slot->tts_tupleDescriptor)
- ExecSetSlotDescriptor(newslot, slot->tts_tupleDescriptor);
- ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
- slot = newslot;
- tuple = newtuple;
- }
- }
-
- /*
- * Check the constraints of the tuple
- *
- * If we generate a new candidate tuple after EvalPlanQual testing, we
- * must loop back here and recheck constraints. (We don't need to redo
- * triggers, however. If there are any BEFORE triggers then trigger.c
- * will have done heap_lock_tuple to lock the correct tuple, so there's no
- * need to do them again.)
- */
-lreplace:;
- if (resultRelationDesc->rd_att->constr)
- ExecConstraints(resultRelInfo, slot, estate);
-
- /*
- * replace the heap tuple
- *
- * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
- * the row to be updated is visible to that snapshot, and throw a can't-
- * serialize error if not. This is a special-case behavior needed for
- * referential integrity updates in serializable transactions.
- */
- result = heap_update(resultRelationDesc, tupleid, tuple,
- &update_ctid, &update_xmax,
- estate->es_output_cid,
- estate->es_crosscheck_snapshot,
- true /* wait for commit */ );
- switch (result)
- {
- case HeapTupleSelfUpdated:
- /* already deleted by self; nothing to do */
- return;
-
- case HeapTupleMayBeUpdated:
- break;
-
- case HeapTupleUpdated:
- if (IsXactIsoLevelSerializable)
- ereport(ERROR,
- (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
- errmsg("could not serialize access due to concurrent update")));
- else if (!ItemPointerEquals(tupleid, &update_ctid))
- {
- TupleTableSlot *epqslot;
-
- epqslot = EvalPlanQual(estate,
- resultRelInfo->ri_RangeTableIndex,
- &update_ctid,
- update_xmax);
- if (!TupIsNull(epqslot))
- {
- *tupleid = update_ctid;
- slot = ExecFilterJunk(estate->es_junkFilter, epqslot);
- tuple = ExecMaterializeSlot(slot);
- goto lreplace;
- }
- }
- /* tuple already deleted; nothing to do */
- return;
-
- default:
- elog(ERROR, "unrecognized heap_update status: %u", result);
- return;
- }
-
- (estate->es_processed)++;
-
- /*
- * Note: instead of having to update the old index tuples associated with
- * the heap tuple, all we do is form and insert new index tuples. This is
- * because UPDATEs are actually DELETEs and INSERTs, and index tuple
- * deletion is done later by VACUUM (see notes in ExecDelete). All we do
- * here is insert new index tuples. -cim 9/27/89
- */
-
- /*
- * insert index entries for tuple
- *
- * Note: heap_update returns the tid (location) of the new tuple in the
- * t_self field.
- *
- * If it's a HOT update, we mustn't insert new index entries.
- */
- if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
- estate, false);
-
- /* AFTER ROW UPDATE Triggers */
- ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
- recheckIndexes);
-
- /* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- ExecProcessReturning(resultRelInfo->ri_projectReturning,
- slot, planSlot, dest);
-}
/*
* ExecRelCheck --- check that tuple meets constraints for result relation
@@ -2218,42 +1487,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
/*
- * ExecProcessReturning --- evaluate a RETURNING list and send to dest
- *
- * projectReturning: RETURNING projection info for current result rel
- * tupleSlot: slot holding tuple actually inserted/updated/deleted
- * planSlot: slot holding tuple returned by top plan node
- * dest: where to send the output
- */
-static void
-ExecProcessReturning(ProjectionInfo *projectReturning,
- TupleTableSlot *tupleSlot,
- TupleTableSlot *planSlot,
- DestReceiver *dest)
-{
- ExprContext *econtext = projectReturning->pi_exprContext;
- TupleTableSlot *retSlot;
-
- /*
- * Reset per-tuple memory context to free any expression evaluation
- * storage allocated in the previous cycle.
- */
- ResetExprContext(econtext);
-
- /* Make tuple and any needed join variables available to ExecProject */
- econtext->ecxt_scantuple = tupleSlot;
- econtext->ecxt_outertuple = planSlot;
-
- /* Compute the RETURNING expressions */
- retSlot = ExecProject(projectReturning, NULL);
-
- /* Send to dest */
- (*dest->receiveSlot) (retSlot, dest);
-
- ExecClearTuple(retSlot);
-}
-
-/*
* Check a modified tuple to see if we want to process its updated version
* under READ COMMITTED rules.
*
@@ -2261,6 +1494,7 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
*
* estate - executor state data
* rti - rangetable index of table containing tuple
+ * subplanstate - portion of plan tree that needs to be re-evaluated
* *tid - t_ctid from the outdated tuple (ie, next updated version)
* priorXmax - t_xmax from the outdated tuple
*
@@ -2272,6 +1506,7 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
*/
TupleTableSlot *
EvalPlanQual(EState *estate, Index rti,
+ PlanState *subplanstate,
ItemPointer tid, TransactionId priorXmax)
{
evalPlanQual *epq;
@@ -2526,10 +1761,10 @@ EvalPlanQual(EState *estate, Index rti,
*
* Note: if we were re-using PlanQual plans via ExecReScan, we'd need to
* instead copy down changeable state from the top plan (including
- * es_result_relation_info, es_junkFilter) and reset locally changeable
+ * es_result_relation_info) and reset locally changeable
* state in the epq (including es_param_exec_vals, es_evTupleNull).
*/
- EvalPlanQualStart(epq, estate, epq->next);
+ EvalPlanQualStart(epq, estate, subplanstate->plan, epq->next);
/*
* free old RTE' tuple, if any, and store target tuple where relation's
@@ -2628,7 +1863,8 @@ EndEvalPlanQual(EState *estate)
* the top-level estate rather than initializing it fresh.
*/
static void
-EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq)
+EvalPlanQualStart(evalPlanQual *epq, EState *estate, Plan *planTree,
+ evalPlanQual *priorepq)
{
EState *epqstate;
int rtsize;
@@ -2690,6 +1926,9 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq)
* Initialize private state information for each SubPlan. We must do this
* before running ExecInitNode on the main query tree, since
* ExecInitSubPlan expects to be able to find these entries.
+ * Some of the SubPlans might not be used in the part of the plan tree
+ * we intend to run, but since it's not easy to tell which, we just
+ * initialize them all.
*/
Assert(epqstate->es_subplanstates == NIL);
foreach(l, estate->es_plannedstmt->subplans)
@@ -2704,11 +1943,11 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq)
}
/*
- * Initialize the private state information for all the nodes in the query
- * tree. This opens files, allocates storage and leaves us ready to start
- * processing tuples.
+ * Initialize the private state information for all the nodes in the
+ * part of the plan tree we need to run. This opens files, allocates
+ * storage and leaves us ready to start processing tuples.
*/
- epq->planstate = ExecInitNode(estate->es_plannedstmt->planTree, epqstate, 0);
+ epq->planstate = ExecInitNode(planTree, epqstate, 0);
MemoryContextSwitchTo(oldcontext);
}