summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorTom Lane2021-03-31 15:52:34 +0000
committerTom Lane2021-03-31 15:52:37 +0000
commit86dc90056dfdbd9d1b891718d2e5614e3e432f35 (patch)
tree8d281c58f67e90961688fd311673fbdb2f8c35c7 /src/backend
parent055fee7eb4dcc78e58672aef146334275e1cc40d (diff)
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes: 1. For UPDATE, the subplan of the ModifyTable node now only delivers the new values of the changed columns (i.e., the expressions computed in the query's SET clause) plus row identity information such as CTID. ModifyTable must re-fetch the original tuple to merge in the old values of any unchanged columns. The core advantage of this is that the changed columns are uniform across all tables of an inherited or partitioned target relation, whereas the other columns might not be. A secondary advantage, when the UPDATE involves joins, is that less data needs to pass through the plan tree. The disadvantage of course is an extra fetch of each tuple to be updated. However, that seems to be very nearly free in context; even worst-case tests don't show it to add more than a couple percent to the total query cost. At some point it might be interesting to combine the re-fetch with the tuple access that ModifyTable must do anyway to mark the old tuple dead; but that would require a good deal of refactoring and it seems it wouldn't buy all that much, so this patch doesn't attempt it. 2. For inherited UPDATE/DELETE, instead of generating a separate subplan for each target relation, we now generate a single subplan that is just exactly like a SELECT's plan, then stick ModifyTable on top of that. To let ModifyTable know which target relation a given incoming row refers to, a tableoid junk column is added to the row identity information. This gets rid of the horrid hack that was inheritance_planner(), eliminating O(N^2) planning cost and memory consumption in cases where there were many unprunable target relations. Point 2 of course requires point 1, so that there is a uniform definition of the non-junk columns to be returned by the subplan. We can't insist on uniform definition of the row identity junk columns however, if we want to keep the ability to have both plain and foreign tables in a partitioning hierarchy. Since it wouldn't scale very far to have every child table have its own row identity column, this patch includes provisions to merge similar row identity columns into one column of the subplan result. In particular, we can merge the whole-row Vars typically used as row identity by FDWs into one column by pretending they are type RECORD. (It's still okay for the actual composite Datums to be labeled with the table's rowtype OID, though.) There is more that can be done to file down residual inefficiencies in this patch, but it seems to be committable now. FDW authors should note several API changes: * The argument list for AddForeignUpdateTargets() has changed, and so has the method it must use for adding junk columns to the query. Call add_row_identity_var() instead of manipulating the parse tree directly. You might want to reconsider exactly what you're adding, too. * PlanDirectModify() must now work a little harder to find the ForeignScan plan node; if the foreign table is part of a partitioning hierarchy then the ForeignScan might not be the direct child of ModifyTable. See postgres_fdw for sample code. * To check whether a relation is a target relation, it's no longer sufficient to compare its relid to root->parse->resultRelation. Instead, check it against all_result_relids or leaf_result_relids, as appropriate. Amit Langote and Tom Lane Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/commands/copyfrom.c1
-rw-r--r--src/backend/commands/explain.c18
-rw-r--r--src/backend/commands/trigger.c18
-rw-r--r--src/backend/executor/README12
-rw-r--r--src/backend/executor/execExpr.c200
-rw-r--r--src/backend/executor/execJunk.c52
-rw-r--r--src/backend/executor/execMain.c8
-rw-r--r--src/backend/executor/execPartition.c14
-rw-r--r--src/backend/executor/nodeModifyTable.c728
-rw-r--r--src/backend/nodes/copyfuncs.c2
-rw-r--r--src/backend/nodes/nodeFuncs.c6
-rw-r--r--src/backend/nodes/outfuncs.c25
-rw-r--r--src/backend/nodes/readfuncs.c2
-rw-r--r--src/backend/optimizer/path/allpaths.c2
-rw-r--r--src/backend/optimizer/path/indxpath.c2
-rw-r--r--src/backend/optimizer/plan/createplan.c87
-rw-r--r--src/backend/optimizer/plan/planmain.c7
-rw-r--r--src/backend/optimizer/plan/planner.c790
-rw-r--r--src/backend/optimizer/plan/setrefs.c55
-rw-r--r--src/backend/optimizer/plan/subselect.c11
-rw-r--r--src/backend/optimizer/prep/prepjointree.c5
-rw-r--r--src/backend/optimizer/prep/preptlist.c134
-rw-r--r--src/backend/optimizer/util/appendinfo.c546
-rw-r--r--src/backend/optimizer/util/inherit.c44
-rw-r--r--src/backend/optimizer/util/pathnode.c63
-rw-r--r--src/backend/optimizer/util/plancat.c19
-rw-r--r--src/backend/optimizer/util/relnode.c38
-rw-r--r--src/backend/rewrite/rewriteHandler.c93
-rw-r--r--src/backend/utils/adt/ruleutils.c6
29 files changed, 1560 insertions, 1428 deletions
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 2ed696d429b..74dbb709fe7 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -666,6 +666,7 @@ CopyFrom(CopyFromState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
+ mtstate->mt_nrels = 1;
mtstate->resultRelInfo = resultRelInfo;
mtstate->rootResultRelInfo = resultRelInfo;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fe75cabdcc0..872aaa7aedc 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2081,7 +2081,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
haschildren = planstate->initPlan ||
outerPlanState(planstate) ||
innerPlanState(planstate) ||
- IsA(plan, ModifyTable) ||
IsA(plan, Append) ||
IsA(plan, MergeAppend) ||
IsA(plan, BitmapAnd) ||
@@ -2114,11 +2113,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
/* special child plans */
switch (nodeTag(plan))
{
- case T_ModifyTable:
- ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans,
- ((ModifyTableState *) planstate)->mt_nplans,
- ancestors, es);
- break;
case T_Append:
ExplainMemberNodes(((AppendState *) planstate)->appendplans,
((AppendState *) planstate)->as_nplans,
@@ -3718,14 +3712,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
}
/* Should we explicitly label target relations? */
- labeltargets = (mtstate->mt_nplans > 1 ||
- (mtstate->mt_nplans == 1 &&
+ labeltargets = (mtstate->mt_nrels > 1 ||
+ (mtstate->mt_nrels == 1 &&
mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
- for (j = 0; j < mtstate->mt_nplans; j++)
+ for (j = 0; j < mtstate->mt_nrels; j++)
{
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
@@ -3820,10 +3814,10 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
double insert_path;
double other_path;
- InstrEndLoop(mtstate->mt_plans[0]->instrument);
+ InstrEndLoop(outerPlanState(mtstate)->instrument);
/* count the number of source rows */
- total = mtstate->mt_plans[0]->instrument->ntuples;
+ total = outerPlanState(mtstate)->instrument->ntuples;
other_path = mtstate->ps.instrument->ntuples2;
insert_path = total - other_path;
@@ -3839,7 +3833,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
}
/*
- * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
+ * Explain the constituent plans of an Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
* The ancestors list should already contain the immediate parent of these
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0ee318b3409..a5ceb1698c0 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2725,20 +2725,22 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
/*
* In READ COMMITTED isolation level it's possible that target tuple
* was changed due to concurrent update. In that case we have a raw
- * subplan output tuple in epqslot_candidate, and need to run it
- * through the junk filter to produce an insertable tuple.
+ * subplan output tuple in epqslot_candidate, and need to form a new
+ * insertable tuple using ExecGetUpdateNewTuple to replace the one we
+ * received in newslot. Neither we nor our callers have any further
+ * interest in the passed-in tuple, so it's okay to overwrite newslot
+ * with the newer data.
*
- * Caution: more than likely, the passed-in slot is the same as the
- * junkfilter's output slot, so we are clobbering the original value
- * of slottuple by doing the filtering. This is OK since neither we
- * nor our caller have any more interest in the prior contents of that
- * slot.
+ * (Typically, newslot was also generated by ExecGetUpdateNewTuple, so
+ * that epqslot_clean will be that same slot and the copy step below
+ * is not needed.)
*/
if (epqslot_candidate != NULL)
{
TupleTableSlot *epqslot_clean;
- epqslot_clean = ExecFilterJunk(relinfo->ri_junkFilter, epqslot_candidate);
+ epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate,
+ oldslot);
if (newslot != epqslot_clean)
ExecCopySlot(newslot, epqslot_clean);
diff --git a/src/backend/executor/README b/src/backend/executor/README
index 3726048c4a7..bf5e70860d5 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -32,10 +32,14 @@ includes a RETURNING clause, the ModifyTable node delivers the computed
RETURNING rows as output, otherwise it returns nothing. Handling INSERT
is pretty straightforward: the tuples returned from the plan tree below
ModifyTable are inserted into the correct result relation. For UPDATE,
-the plan tree returns the computed tuples to be updated, plus a "junk"
-(hidden) CTID column identifying which table row is to be replaced by each
-one. For DELETE, the plan tree need only deliver a CTID column, and the
-ModifyTable node visits each of those rows and marks the row deleted.
+the plan tree returns the new values of the updated columns, plus "junk"
+(hidden) column(s) identifying which table row is to be updated. The
+ModifyTable node must fetch that row to extract values for the unchanged
+columns, combine the values into a new row, and apply the update. (For a
+heap table, the row-identity junk column is a CTID, but other things may
+be used for other table types.) For DELETE, the plan tree need only deliver
+junk row-identity column(s), and the ModifyTable node visits each of those
+rows and marks the row deleted.
XXX a great deal more documentation needs to be written here...
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 2e463f54990..e33231f7be8 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -478,6 +478,206 @@ ExecBuildProjectionInfo(List *targetList,
}
/*
+ * ExecBuildUpdateProjection
+ *
+ * Build a ProjectionInfo node for constructing a new tuple during UPDATE.
+ * The projection will be executed in the given econtext and the result will
+ * be stored into the given tuple slot. (Caller must have ensured that tuple
+ * slot has a descriptor matching the target rel!)
+ *
+ * subTargetList is the tlist of the subplan node feeding ModifyTable.
+ * We use this mainly to cross-check that the expressions being assigned
+ * are of the correct types. The values from this tlist are assumed to be
+ * available from the "outer" tuple slot. They are assigned to target columns
+ * listed in the corresponding targetColnos elements. (Only non-resjunk tlist
+ * entries are assigned.) Columns not listed in targetColnos are filled from
+ * the UPDATE's old tuple, which is assumed to be available in the "scan"
+ * tuple slot.
+ *
+ * relDesc must describe the relation we intend to update.
+ *
+ * This is basically a specialized variant of ExecBuildProjectionInfo.
+ * However, it also performs sanity checks equivalent to ExecCheckPlanOutput.
+ * Since we never make a normal tlist equivalent to the whole
+ * tuple-to-be-assigned, there is no convenient way to apply
+ * ExecCheckPlanOutput, so we must do our safety checks here.
+ */
+ProjectionInfo *
+ExecBuildUpdateProjection(List *subTargetList,
+ List *targetColnos,
+ TupleDesc relDesc,
+ ExprContext *econtext,
+ TupleTableSlot *slot,
+ PlanState *parent)
+{
+ ProjectionInfo *projInfo = makeNode(ProjectionInfo);
+ ExprState *state;
+ int nAssignableCols;
+ bool sawJunk;
+ Bitmapset *assignedCols;
+ LastAttnumInfo deform = {0, 0, 0};
+ ExprEvalStep scratch = {0};
+ int outerattnum;
+ ListCell *lc,
+ *lc2;
+
+ projInfo->pi_exprContext = econtext;
+ /* We embed ExprState into ProjectionInfo instead of doing extra palloc */
+ projInfo->pi_state.tag = T_ExprState;
+ state = &projInfo->pi_state;
+ state->expr = NULL; /* not used */
+ state->parent = parent;
+ state->ext_params = NULL;
+
+ state->resultslot = slot;
+
+ /*
+ * Examine the subplan tlist to see how many non-junk columns there are,
+ * and to verify that the non-junk columns come before the junk ones.
+ */
+ nAssignableCols = 0;
+ sawJunk = false;
+ foreach(lc, subTargetList)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+
+ if (tle->resjunk)
+ sawJunk = true;
+ else
+ {
+ if (sawJunk)
+ elog(ERROR, "subplan target list is out of order");
+ nAssignableCols++;
+ }
+ }
+
+ /* We should have one targetColnos entry per non-junk column */
+ if (nAssignableCols != list_length(targetColnos))
+ elog(ERROR, "targetColnos does not match subplan target list");
+
+ /*
+ * Build a bitmapset of the columns in targetColnos. (We could just use
+ * list_member_int() tests, but that risks O(N^2) behavior with many
+ * columns.)
+ */
+ assignedCols = NULL;
+ foreach(lc, targetColnos)
+ {
+ AttrNumber targetattnum = lfirst_int(lc);
+
+ assignedCols = bms_add_member(assignedCols, targetattnum);
+ }
+
+ /*
+ * We want to insert EEOP_*_FETCHSOME steps to ensure the outer and scan
+ * tuples are sufficiently deconstructed. Outer tuple is easy, but for
+ * scan tuple we must find out the last old column we need.
+ */
+ deform.last_outer = nAssignableCols;
+
+ for (int attnum = relDesc->natts; attnum > 0; attnum--)
+ {
+ Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
+
+ if (attr->attisdropped)
+ continue;
+ if (bms_is_member(attnum, assignedCols))
+ continue;
+ deform.last_scan = attnum;
+ break;
+ }
+
+ ExecPushExprSlots(state, &deform);
+
+ /*
+ * Now generate code to fetch data from the outer tuple, incidentally
+ * validating that it'll be of the right type. The checks above ensure
+ * that the forboth() will iterate over exactly the non-junk columns.
+ */
+ outerattnum = 0;
+ forboth(lc, subTargetList, lc2, targetColnos)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ AttrNumber targetattnum = lfirst_int(lc2);
+ Form_pg_attribute attr;
+
+ Assert(!tle->resjunk);
+
+ /*
+ * Apply sanity checks comparable to ExecCheckPlanOutput().
+ */
+ if (targetattnum <= 0 || targetattnum > relDesc->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 = TupleDescAttr(relDesc, targetattnum - 1);
+
+ if (attr->attisdropped)
+ 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.",
+ targetattnum)));
+ 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),
+ targetattnum,
+ format_type_be(exprType((Node *) tle->expr)))));
+
+ /*
+ * OK, build an outer-tuple reference.
+ */
+ scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
+ scratch.d.assign_var.attnum = outerattnum++;
+ scratch.d.assign_var.resultnum = targetattnum - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+
+ /*
+ * Now generate code to copy over any old columns that were not assigned
+ * to, and to ensure that dropped columns are set to NULL.
+ */
+ for (int attnum = 1; attnum <= relDesc->natts; attnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
+
+ if (attr->attisdropped)
+ {
+ /* Put a null into the ExprState's resvalue/resnull ... */
+ scratch.opcode = EEOP_CONST;
+ scratch.resvalue = &state->resvalue;
+ scratch.resnull = &state->resnull;
+ scratch.d.constval.value = (Datum) 0;
+ scratch.d.constval.isnull = true;
+ ExprEvalPushStep(state, &scratch);
+ /* ... then assign it to the result slot */
+ scratch.opcode = EEOP_ASSIGN_TMP;
+ scratch.d.assign_tmp.resultnum = attnum - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+ else if (!bms_is_member(attnum, assignedCols))
+ {
+ /* Certainly the right type, so needn't check */
+ scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
+ scratch.d.assign_var.attnum = attnum - 1;
+ scratch.d.assign_var.resultnum = attnum - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+ }
+
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return projInfo;
+}
+
+/*
* ExecPrepareExpr --- initialize for expression execution outside a normal
* Plan tree context.
*
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index 970e1c325e3..9741897e838 100644
--- a/src/backend/executor/execJunk.c
+++ b/src/backend/executor/execJunk.c
@@ -59,7 +59,10 @@
JunkFilter *
ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
{
+ JunkFilter *junkfilter;
TupleDesc cleanTupType;
+ int cleanLength;
+ AttrNumber *cleanMap;
/*
* Compute the tuple descriptor for the cleaned tuple.
@@ -67,36 +70,6 @@ ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
cleanTupType = ExecCleanTypeFromTL(targetList);
/*
- * The rest is the same as ExecInitJunkFilterInsertion, ie, we want to map
- * every non-junk targetlist column into the output tuple.
- */
- return ExecInitJunkFilterInsertion(targetList, cleanTupType, slot);
-}
-
-/*
- * ExecInitJunkFilterInsertion
- *
- * Initialize a JunkFilter for insertions into a table.
- *
- * Here, we are given the target "clean" tuple descriptor rather than
- * inferring it from the targetlist. Although the target descriptor can
- * contain deleted columns, that is not of concern here, since the targetlist
- * should contain corresponding NULL constants (cf. ExecCheckPlanOutput).
- * It is assumed that the caller has checked that the table's columns match up
- * with the non-junk columns of the targetlist.
- */
-JunkFilter *
-ExecInitJunkFilterInsertion(List *targetList,
- TupleDesc cleanTupType,
- TupleTableSlot *slot)
-{
- JunkFilter *junkfilter;
- int cleanLength;
- AttrNumber *cleanMap;
- ListCell *t;
- AttrNumber cleanResno;
-
- /*
* Use the given slot, or make a new slot if we weren't given one.
*/
if (slot)
@@ -117,6 +90,9 @@ ExecInitJunkFilterInsertion(List *targetList,
cleanLength = cleanTupType->natts;
if (cleanLength > 0)
{
+ AttrNumber cleanResno;
+ ListCell *t;
+
cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber));
cleanResno = 0;
foreach(t, targetList)
@@ -263,22 +239,6 @@ ExecFindJunkAttributeInTlist(List *targetlist, const char *attrName)
}
/*
- * ExecGetJunkAttribute
- *
- * Given a junk filter's input tuple (slot) and a junk attribute's number
- * previously found by ExecFindJunkAttribute, extract & return the value and
- * isNull flag of the attribute.
- */
-Datum
-ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno,
- bool *isNull)
-{
- Assert(attno > 0);
-
- return slot_getattr(slot, attno, isNull);
-}
-
-/*
* ExecFilterJunk
*
* Construct and return a slot with all the junk attributes removed.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 8de78ada631..163242f54eb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1217,11 +1217,14 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_FdwRoutine = NULL;
/* The following fields are set later if needed */
+ resultRelInfo->ri_RowIdAttNo = 0;
+ resultRelInfo->ri_projectNew = NULL;
+ resultRelInfo->ri_newTupleSlot = NULL;
+ resultRelInfo->ri_oldTupleSlot = NULL;
resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_GeneratedExprs = NULL;
- resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
resultRelInfo->ri_onConflictArbiterIndexes = NIL;
resultRelInfo->ri_onConflict = NULL;
@@ -2413,7 +2416,8 @@ EvalPlanQualInit(EPQState *epqstate, EState *parentestate,
/*
* EvalPlanQualSetPlan -- set or change subplan of an EPQState.
*
- * We need this so that ModifyTable can deal with multiple subplans.
+ * We used to need this so that ModifyTable could deal with multiple subplans.
+ * It could now be refactored out of existence.
*/
void
EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 619aaffae43..558060e080f 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -82,7 +82,7 @@
*
* subplan_resultrel_htab
* Hash table to store subplan ResultRelInfos by Oid. This is used to
- * cache ResultRelInfos from subplans of an UPDATE ModifyTable node;
+ * cache ResultRelInfos from targets of an UPDATE ModifyTable node;
* NULL in other cases. Some of these may be useful for tuple routing
* to save having to build duplicates.
*
@@ -527,12 +527,12 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
ctl.entrysize = sizeof(SubplanResultRelHashElem);
ctl.hcxt = CurrentMemoryContext;
- htab = hash_create("PartitionTupleRouting table", mtstate->mt_nplans,
+ htab = hash_create("PartitionTupleRouting table", mtstate->mt_nrels,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
/* Hash all subplans by their Oid */
- for (i = 0; i < mtstate->mt_nplans; i++)
+ for (i = 0; i < mtstate->mt_nrels; i++)
{
ResultRelInfo *rri = &mtstate->resultRelInfo[i];
bool found;
@@ -628,10 +628,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
Assert((node->operation == CMD_INSERT &&
list_length(node->withCheckOptionLists) == 1 &&
- list_length(node->plans) == 1) ||
+ list_length(node->resultRelations) == 1) ||
(node->operation == CMD_UPDATE &&
list_length(node->withCheckOptionLists) ==
- list_length(node->plans)));
+ list_length(node->resultRelations)));
/*
* Use the WCO list of the first plan as a reference to calculate
@@ -687,10 +687,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
list_length(node->returningLists) == 1 &&
- list_length(node->plans) == 1) ||
+ list_length(node->resultRelations) == 1) ||
(node->operation == CMD_UPDATE &&
list_length(node->returningLists) ==
- list_length(node->plans)));
+ list_length(node->resultRelations)));
/*
* Use the RETURNING list of the first plan as a reference to
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2993ba43e32..bf65785e643 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -19,14 +19,10 @@
* ExecReScanModifyTable - rescan the ModifyTable node
*
* NOTES
- * Each ModifyTable node contains a list of one or more subplans,
- * much like an Append node. There is one subplan per result relation.
- * The key reason for this is that in an inherited UPDATE command, each
- * result relation could have a different schema (more or different
- * columns) requiring a different plan tree to produce it. In an
- * inherited DELETE, all the subplans should produce the same output
- * rowtype, but we might still find that different plans are appropriate
- * for different child relations.
+ * The ModifyTable node receives input from its outerPlan, which is
+ * the data to insert for INSERT cases, or the changed columns' new
+ * values plus row-locating info for UPDATE cases, or just the
+ * row-locating info for DELETE cases.
*
* If the query specifies RETURNING, then the ModifyTable returns a
* RETURNING tuple after completing each row insert, update, or delete.
@@ -58,6 +54,12 @@
#include "utils/rel.h"
+typedef struct MTTargetRelLookup
+{
+ Oid relationOid; /* hash key, must be first */
+ int relationIndex; /* rel's index in resultRelInfo[] array */
+} MTTargetRelLookup;
+
static void ExecBatchInsert(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
TupleTableSlot **slots,
@@ -81,7 +83,7 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo **partRelInfo);
/*
- * Verify that the tuples to be produced by INSERT or UPDATE match the
+ * Verify that the tuples to be produced by INSERT match the
* target relation's rowtype
*
* We do this to guard against stale plans. If plan invalidation is
@@ -91,6 +93,9 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
*
* The plan output is represented by its targetlist, because that makes
* handling the dropped-column case easier.
+ *
+ * We used to use this for UPDATE as well, but now the equivalent checks
+ * are done in ExecBuildUpdateProjection.
*/
static void
ExecCheckPlanOutput(Relation resultRel, List *targetList)
@@ -104,8 +109,7 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
TargetEntry *tle = (TargetEntry *) lfirst(lc);
Form_pg_attribute attr;
- if (tle->resjunk)
- continue; /* ignore junk tlist items */
+ Assert(!tle->resjunk); /* caller removed junk items already */
if (attno >= resultDesc->natts)
ereport(ERROR,
@@ -367,6 +371,74 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
MemoryContextSwitchTo(oldContext);
}
+/*
+ * ExecGetInsertNewTuple
+ * This prepares a "new" tuple ready to be inserted into given result
+ * relation, by removing any junk columns of the plan's output tuple
+ * and (if necessary) coercing the tuple to the right tuple format.
+ */
+static TupleTableSlot *
+ExecGetInsertNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot)
+{
+ ProjectionInfo *newProj = relinfo->ri_projectNew;
+ ExprContext *econtext;
+
+ /*
+ * If there's no projection to be done, just make sure the slot is of the
+ * right type for the target rel. If the planSlot is the right type we
+ * can use it as-is, else copy the data into ri_newTupleSlot.
+ */
+ if (newProj == NULL)
+ {
+ if (relinfo->ri_newTupleSlot->tts_ops != planSlot->tts_ops)
+ {
+ ExecCopySlot(relinfo->ri_newTupleSlot, planSlot);
+ return relinfo->ri_newTupleSlot;
+ }
+ else
+ return planSlot;
+ }
+
+ /*
+ * Else project; since the projection output slot is ri_newTupleSlot, this
+ * will also fix any slot-type problem.
+ *
+ * Note: currently, this is dead code, because INSERT cases don't receive
+ * any junk columns so there's never a projection to be done.
+ */
+ econtext = newProj->pi_exprContext;
+ econtext->ecxt_outertuple = planSlot;
+ return ExecProject(newProj);
+}
+
+/*
+ * ExecGetUpdateNewTuple
+ * This prepares a "new" tuple by combining an UPDATE subplan's output
+ * tuple (which contains values of changed columns) with unchanged
+ * columns taken from the old tuple.
+ *
+ * The subplan tuple might also contain junk columns, which are ignored.
+ * Note that the projection also ensures we have a slot of the right type.
+ */
+TupleTableSlot *
+ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ TupleTableSlot *oldSlot)
+{
+ ProjectionInfo *newProj = relinfo->ri_projectNew;
+ ExprContext *econtext;
+
+ Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
+ Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
+
+ econtext = newProj->pi_exprContext;
+ econtext->ecxt_outertuple = planSlot;
+ econtext->ecxt_scantuple = oldSlot;
+ return ExecProject(newProj);
+}
+
+
/* ----------------------------------------------------------------
* ExecInsert
*
@@ -374,6 +446,10 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
* (or partition thereof) and insert appropriate tuples into the index
* relations.
*
+ * slot contains the new tuple value to be stored.
+ * planSlot is the output of the ModifyTable's subplan; we use it
+ * to access "junk" columns that are not going to be stored.
+ *
* Returns RETURNING result if any, otherwise NULL.
*
* This may change the currently active tuple conversion map in
@@ -1269,13 +1345,22 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
return true;
else
{
- *retry_slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+ /* Fetch the most recent version of old tuple. */
+ TupleTableSlot *oldSlot = resultRelInfo->ri_oldTupleSlot;
+
+ if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
+ tupleid,
+ SnapshotAny,
+ oldSlot))
+ elog(ERROR, "failed to fetch tuple being updated");
+ *retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
+ oldSlot);
return false;
}
}
/*
- * resultRelInfo is one of the per-subplan resultRelInfos. So we should
+ * resultRelInfo is one of the per-relation resultRelInfos. So we should
* convert the tuple into root's tuple descriptor if needed, since
* ExecInsert() starts the search from root.
*/
@@ -1319,6 +1404,11 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
* foreign table triggers; it is NULL when the foreign table has
* no relevant triggers.
*
+ * slot contains the new tuple value to be stored.
+ * planSlot is the output of the ModifyTable's subplan; we use it
+ * to access values from other input tables (for RETURNING),
+ * row-ID junk columns, etc.
+ *
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
@@ -1545,6 +1635,7 @@ lreplace:;
{
TupleTableSlot *inputslot;
TupleTableSlot *epqslot;
+ TupleTableSlot *oldSlot;
if (IsolationUsesXactSnapshot())
ereport(ERROR,
@@ -1578,7 +1669,15 @@ lreplace:;
/* Tuple not passing quals anymore, exiting... */
return NULL;
- slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+ /* Fetch the most recent version of old tuple. */
+ oldSlot = resultRelInfo->ri_oldTupleSlot;
+ if (!table_tuple_fetch_row_version(resultRelationDesc,
+ tupleid,
+ SnapshotAny,
+ oldSlot))
+ elog(ERROR, "failed to fetch tuple being updated");
+ slot = ExecGetUpdateNewTuple(resultRelInfo,
+ epqslot, oldSlot);
goto lreplace;
case TM_Deleted:
@@ -2051,16 +2150,16 @@ ExecModifyTable(PlanState *pstate)
CmdType operation = node->operation;
ResultRelInfo *resultRelInfo;
PlanState *subplanstate;
- JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
+ TupleTableSlot *oldSlot;
ItemPointer tupleid;
ItemPointerData tuple_ctid;
HeapTupleData oldtupdata;
HeapTuple oldtuple;
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
- List *relinfos = NIL;
- ListCell *lc;
+ List *relinfos = NIL;
+ ListCell *lc;
CHECK_FOR_INTERRUPTS();
@@ -2095,12 +2194,11 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
- resultRelInfo = node->resultRelInfo + node->mt_whichplan;
- subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
+ resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;
+ subplanstate = outerPlanState(node);
/*
- * Fetch rows from subplan(s), and execute the required table modification
+ * Fetch rows from subplan, and execute the required table modification
* for each row.
*/
for (;;)
@@ -2123,30 +2221,61 @@ ExecModifyTable(PlanState *pstate)
planSlot = ExecProcNode(subplanstate);
+ /* No more tuples to process? */
if (TupIsNull(planSlot))
- {
- /* advance to next subplan if any */
- node->mt_whichplan++;
- if (node->mt_whichplan < node->mt_nplans)
- {
- resultRelInfo++;
- subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = resultRelInfo->ri_junkFilter;
- EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
- node->mt_arowmarks[node->mt_whichplan]);
- continue;
- }
- else
- break;
- }
+ break;
/*
- * Ensure input tuple is the right format for the target relation.
+ * When there are multiple result relations, each tuple contains a
+ * junk column that gives the OID of the rel from which it came.
+ * Extract it and select the correct result relation.
*/
- if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
+ if (AttributeNumberIsValid(node->mt_resultOidAttno))
{
- ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
- planSlot = node->mt_scans[node->mt_whichplan];
+ Datum datum;
+ bool isNull;
+ Oid resultoid;
+
+ datum = ExecGetJunkAttribute(planSlot, node->mt_resultOidAttno,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "tableoid is NULL");
+ resultoid = DatumGetObjectId(datum);
+
+ /* If it's not the same as last time, we need to locate the rel */
+ if (resultoid != node->mt_lastResultOid)
+ {
+ if (node->mt_resultOidHash)
+ {
+ /* Use the pre-built hash table to locate the rel */
+ MTTargetRelLookup *mtlookup;
+
+ mtlookup = (MTTargetRelLookup *)
+ hash_search(node->mt_resultOidHash, &resultoid,
+ HASH_FIND, NULL);
+ if (!mtlookup)
+ elog(ERROR, "incorrect result rel OID %u", resultoid);
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = mtlookup->relationIndex;
+ resultRelInfo = node->resultRelInfo + mtlookup->relationIndex;
+ }
+ else
+ {
+ /* With few target rels, just do a simple search */
+ int ndx;
+
+ for (ndx = 0; ndx < node->mt_nrels; ndx++)
+ {
+ resultRelInfo = node->resultRelInfo + ndx;
+ if (RelationGetRelid(resultRelInfo->ri_RelationDesc) == resultoid)
+ break;
+ }
+ if (ndx >= node->mt_nrels)
+ elog(ERROR, "incorrect result rel OID %u", resultoid);
+ node->mt_lastResultOid = resultoid;
+ node->mt_lastResultIndex = ndx;
+ }
+ }
}
/*
@@ -2173,84 +2302,116 @@ ExecModifyTable(PlanState *pstate)
tupleid = NULL;
oldtuple = NULL;
- if (junkfilter != NULL)
+
+ /*
+ * For UPDATE/DELETE, fetch the row identity info for the tuple to be
+ * updated/deleted. For a heap relation, that's a TID; otherwise we
+ * may have a wholerow junk attr that carries the old tuple in toto.
+ * Keep this in step with the part of ExecInitModifyTable that sets up
+ * ri_RowIdAttNo.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
- /*
- * extract the 'ctid' or 'wholerow' junk attribute.
- */
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ char relkind;
+ Datum datum;
+ bool isNull;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
{
- char relkind;
- Datum datum;
- bool isNull;
-
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
- {
- 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; /* be sure we don't free ctid!! */
- tupleid = &tuple_ctid;
- }
-
- /*
- * Use the wholerow attribute, when available, to reconstruct
- * the old relation tuple.
- *
- * Foreign table updates have a wholerow attribute when the
- * relation has a row-level trigger. Note that the wholerow
- * attribute does not carry system columns. Foreign table
- * triggers miss seeing those, except that we know enough here
- * to set t_tableOid. Quite separately from this, the FDW may
- * fetch its own junk attrs to identify the row.
- *
- * Other relevant relkinds, currently limited to views, always
- * have a wholerow attribute.
- */
- else if (AttributeNumberIsValid(junkfilter->jf_junkAttNo))
- {
- datum = ExecGetJunkAttribute(slot,
- junkfilter->jf_junkAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "wholerow is NULL");
-
- oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
- oldtupdata.t_len =
- HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
- ItemPointerSetInvalid(&(oldtupdata.t_self));
- /* Historically, view triggers see invalid t_tableOid. */
- oldtupdata.t_tableOid =
- (relkind == RELKIND_VIEW) ? InvalidOid :
- RelationGetRelid(resultRelInfo->ri_RelationDesc);
-
- oldtuple = &oldtupdata;
- }
- else
- Assert(relkind == RELKIND_FOREIGN_TABLE);
+ /* ri_RowIdAttNo refers to a ctid attribute */
+ Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo));
+ datum = ExecGetJunkAttribute(slot,
+ resultRelInfo->ri_RowIdAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "ctid is NULL");
+
+ tupleid = (ItemPointer) DatumGetPointer(datum);
+ tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
+ tupleid = &tuple_ctid;
}
/*
- * apply the junkfilter if needed.
+ * Use the wholerow attribute, when available, to reconstruct the
+ * old relation tuple. The old tuple serves one or both of two
+ * purposes: 1) it serves as the OLD tuple for row triggers, 2) it
+ * provides values for any unchanged columns for the NEW tuple of
+ * an UPDATE, because the subplan does not produce all the columns
+ * of the target table.
+ *
+ * Note that the wholerow attribute does not carry system columns,
+ * so foreign table triggers miss seeing those, except that we
+ * know enough here to set t_tableOid. Quite separately from
+ * this, the FDW may fetch its own junk attrs to identify the row.
+ *
+ * Other relevant relkinds, currently limited to views, always
+ * have a wholerow attribute.
*/
- if (operation != CMD_DELETE)
- slot = ExecFilterJunk(junkfilter, slot);
+ else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ {
+ datum = ExecGetJunkAttribute(slot,
+ resultRelInfo->ri_RowIdAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "wholerow is NULL");
+
+ oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
+ oldtupdata.t_len =
+ HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
+ ItemPointerSetInvalid(&(oldtupdata.t_self));
+ /* Historically, view triggers see invalid t_tableOid. */
+ oldtupdata.t_tableOid =
+ (relkind == RELKIND_VIEW) ? InvalidOid :
+ RelationGetRelid(resultRelInfo->ri_RelationDesc);
+
+ oldtuple = &oldtupdata;
+ }
+ else
+ {
+ /* Only foreign tables are allowed to omit a row-ID attr */
+ Assert(relkind == RELKIND_FOREIGN_TABLE);
+ }
}
switch (operation)
{
case CMD_INSERT:
+ slot = ExecGetInsertNewTuple(resultRelInfo, planSlot);
slot = ExecInsert(node, resultRelInfo, slot, planSlot,
estate, node->canSetTag);
break;
case CMD_UPDATE:
+
+ /*
+ * Make the new tuple by combining plan's output tuple with
+ * the old tuple being updated.
+ */
+ oldSlot = resultRelInfo->ri_oldTupleSlot;
+ if (oldtuple != NULL)
+ {
+ /* Use the wholerow junk attr as the old tuple. */
+ ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
+ }
+ else
+ {
+ /* Fetch the most recent version of old tuple. */
+ Relation relation = resultRelInfo->ri_RelationDesc;
+
+ Assert(tupleid != NULL);
+ if (!table_tuple_fetch_row_version(relation, tupleid,
+ SnapshotAny,
+ oldSlot))
+ elog(ERROR, "failed to fetch tuple being updated");
+ }
+ slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot,
+ oldSlot);
+
+ /* Now apply the update. */
slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, slot,
planSlot, &node->mt_epqstate, estate,
node->canSetTag);
@@ -2313,12 +2474,12 @@ ModifyTableState *
ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
ModifyTableState *mtstate;
+ Plan *subplan = outerPlan(node);
CmdType operation = node->operation;
- int nplans = list_length(node->plans);
+ int nrels = list_length(node->resultRelations);
ResultRelInfo *resultRelInfo;
- Plan *subplan;
- ListCell *l,
- *l1;
+ List *arowmarks;
+ ListCell *l;
int i;
Relation rel;
bool update_tuple_routing_needed = node->partColsUpdated;
@@ -2338,10 +2499,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
- mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ mtstate->mt_nrels = nrels;
mtstate->resultRelInfo = (ResultRelInfo *)
- palloc(nplans * sizeof(ResultRelInfo));
- mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+ palloc(nrels * sizeof(ResultRelInfo));
/*----------
* Resolve the target relation. This is the same as:
@@ -2370,9 +2530,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
linitial_int(node->resultRelations));
}
- mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
- mtstate->mt_nplans = nplans;
-
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
mtstate->fireBSTriggers = true;
@@ -2385,23 +2542,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
- * call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". This is also a convenient place to
- * verify that the proposed target relations are valid and open their
- * indexes for insertion of new index entries.
+ * Open all the result relations and initialize the ResultRelInfo structs.
+ * (But root relation was initialized above, if it's part of the array.)
+ * We must do this before initializing the subplan, because direct-modify
+ * FDWs expect their ResultRelInfos to be available.
*/
resultRelInfo = mtstate->resultRelInfo;
i = 0;
- forboth(l, node->resultRelations, l1, node->plans)
+ foreach(l, node->resultRelations)
{
Index resultRelation = lfirst_int(l);
- subplan = (Plan *) lfirst(l1);
-
- /*
- * This opens result relation and fills ResultRelInfo. (root relation
- * was initialized already.)
- */
if (resultRelInfo != mtstate->rootResultRelInfo)
ExecInitResultRelation(estate, resultRelInfo, resultRelation);
@@ -2414,6 +2565,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
CheckValidResultRel(resultRelInfo, operation);
+ resultRelInfo++;
+ i++;
+ }
+
+ /*
+ * Now we may initialize the subplan.
+ */
+ outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
+
+ /*
+ * Do additional per-result-relation initialization.
+ */
+ for (i = 0; i < nrels; i++)
+ {
+ resultRelInfo = &mtstate->resultRelInfo[i];
+
/*
* If there are indices on the result relation, open them and save
* descriptors in the result relation info, so that we can add new
@@ -2439,12 +2606,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
operation == CMD_UPDATE)
update_tuple_routing_needed = true;
- /* Now init the plan for this result rel */
- mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- mtstate->mt_scans[i] =
- ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
/* Also let FDWs init themselves for foreign-table result rels */
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
@@ -2476,11 +2637,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc));
- resultRelInfo++;
- i++;
}
- /* Get the target relation */
+ /* Get the root target relation */
rel = mtstate->rootResultRelInfo->ri_RelationDesc;
/*
@@ -2596,8 +2755,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
TupleDesc relationDesc;
TupleDesc tupDesc;
- /* insert may only have one plan, inheritance is not expanded */
- Assert(nplans == 1);
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(nrels == 1);
/* already exists if created by RETURNING processing above */
if (mtstate->ps.ps_ExprContext == NULL)
@@ -2649,149 +2808,223 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* EvalPlanQual mechanism needs to be told about them. Locate the
* relevant ExecRowMarks.
*/
+ arowmarks = NIL;
foreach(l, node->rowMarks)
{
PlanRowMark *rc = lfirst_node(PlanRowMark, l);
ExecRowMark *erm;
+ ExecAuxRowMark *aerm;
/* ignore "parent" rowmarks; they are irrelevant at runtime */
if (rc->isParent)
continue;
- /* find ExecRowMark (same for all subplans) */
+ /* Find ExecRowMark and build ExecAuxRowMark */
erm = ExecFindRowMark(estate, rc->rti, false);
-
- /* build ExecAuxRowMark for each subplan */
- for (i = 0; i < nplans; i++)
- {
- ExecAuxRowMark *aerm;
-
- subplan = mtstate->mt_plans[i]->plan;
- aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
- mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
- }
+ aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
+ arowmarks = lappend(arowmarks, aerm);
}
- /* select first subplan */
- mtstate->mt_whichplan = 0;
- subplan = (Plan *) linitial(node->plans);
- EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
- mtstate->mt_arowmarks[0]);
+ EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
/*
- * Initialize the junk filter(s) if needed. 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 at least one junk attribute present
- * --- no need to look first. Typically, this will be a 'ctid' or
- * 'wholerow' attribute, but in the case of a foreign data wrapper it
- * might be a set of junk attributes sufficient to identify the remote
- * row.
+ * Initialize projection(s) to create tuples suitable for result rel(s).
+ * INSERT queries may need a projection to filter out junk attrs in the
+ * tlist. UPDATE always needs a projection, because (1) there's always
+ * some junk attrs, and (2) we may need to merge values of not-updated
+ * columns from the old tuple into the final tuple. In UPDATE, the tuple
+ * arriving from the subplan contains only new values for the changed
+ * columns, plus row identity info in the junk attrs.
*
- * If there are multiple result relations, each one needs its own junk
- * filter. Note multiple rels are only possible for UPDATE/DELETE, so we
- * can't be fooled by some needing a filter and some not.
+ * If there are multiple result relations, each one needs its own
+ * projection. Note multiple rels are only possible for UPDATE/DELETE, so
+ * we can't be fooled by some needing a projection and some not.
*
* This section of code is also a convenient place to verify that the
* output of an INSERT or UPDATE matches the target table(s).
*/
+ for (i = 0; i < nrels; i++)
{
- bool junk_filter_needed = false;
+ resultRelInfo = &mtstate->resultRelInfo[i];
- switch (operation)
+ /*
+ * Prepare to generate tuples suitable for the target relation.
+ */
+ if (operation == CMD_INSERT)
{
- case CMD_INSERT:
- foreach(l, subplan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
- break;
- case CMD_UPDATE:
- case CMD_DELETE:
- junk_filter_needed = true;
- break;
- default:
- elog(ERROR, "unknown operation");
- break;
- }
+ List *insertTargetList = NIL;
+ bool need_projection = false;
- if (junk_filter_needed)
- {
- resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
+ foreach(l, subplan->targetlist)
{
- JunkFilter *j;
- TupleTableSlot *junkresslot;
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
- subplan = mtstate->mt_plans[i]->plan;
+ if (!tle->resjunk)
+ insertTargetList = lappend(insertTargetList, tle);
+ else
+ need_projection = true;
+ }
- junkresslot =
- ExecInitExtraTupleSlot(estate, NULL,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ /*
+ * The junk-free list must produce a tuple suitable for the result
+ * relation.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ insertTargetList);
- /*
- * For an INSERT or UPDATE, the result tuple must always match
- * the target table's descriptor. For a DELETE, it won't
- * (indeed, there's probably no non-junk output columns).
- */
- if (operation == CMD_INSERT || operation == CMD_UPDATE)
- {
- ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
- j = ExecInitJunkFilterInsertion(subplan->targetlist,
- RelationGetDescr(resultRelInfo->ri_RelationDesc),
- junkresslot);
- }
- else
- j = ExecInitJunkFilter(subplan->targetlist,
- junkresslot);
+ /* We'll need a slot matching the table's format. */
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
- if (operation == CMD_UPDATE || operation == CMD_DELETE)
- {
- /* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
+ /* Build ProjectionInfo if needed (it probably isn't). */
+ if (need_projection)
+ {
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildProjectionInfo(insertTargetList,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps,
+ relDesc);
+ }
+ }
+ else if (operation == CMD_UPDATE)
+ {
+ List *updateColnos;
+ TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
- relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION ||
- relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_PARTITIONED_TABLE)
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
- }
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * When there is a row-level trigger, there should be
- * a wholerow attribute.
- */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- }
- else
- {
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk wholerow column");
- }
- }
+ updateColnos = (List *) list_nth(node->updateColnosLists, i);
- resultRelInfo->ri_junkFilter = j;
- resultRelInfo++;
+ /*
+ * For UPDATE, we use the old tuple to fill up missing values in
+ * the tuple produced by the plan to get the new tuple. We need
+ * two slots, both matching the table's desired format.
+ */
+ resultRelInfo->ri_oldTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+ resultRelInfo->ri_newTupleSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
+
+ /* need an expression context to do the projection */
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+
+ resultRelInfo->ri_projectNew =
+ ExecBuildUpdateProjection(subplan->targetlist,
+ updateColnos,
+ relDesc,
+ mtstate->ps.ps_ExprContext,
+ resultRelInfo->ri_newTupleSlot,
+ &mtstate->ps);
+ }
+
+ /*
+ * For UPDATE/DELETE, find the appropriate junk attr now, either a
+ * 'ctid' or 'wholerow' attribute depending on relkind. For foreign
+ * tables, the FDW might have created additional junk attr(s), but
+ * those are no concern of ours.
+ */
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ {
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * When there is a row-level trigger, there should be a
+ * wholerow attribute. We also require it to be present in
+ * UPDATE, so we can get the values of unchanged columns.
+ */
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ if (mtstate->operation == CMD_UPDATE &&
+ !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
+ else
+ {
+ /* Other valid target relkinds must provide wholerow */
+ resultRelInfo->ri_RowIdAttNo =
+ ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "wholerow");
+ if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
+ elog(ERROR, "could not find junk wholerow column");
}
}
- else
+ }
+
+ /*
+ * If this is an inherited update/delete, there will be a junk attribute
+ * named "tableoid" present in the subplan's targetlist. It will be used
+ * to identify the result relation for a given tuple to be
+ * updated/deleted.
+ */
+ mtstate->mt_resultOidAttno =
+ ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
+ Assert(AttributeNumberIsValid(mtstate->mt_resultOidAttno) || nrels == 1);
+ mtstate->mt_lastResultOid = InvalidOid; /* force lookup at first tuple */
+ mtstate->mt_lastResultIndex = 0; /* must be zero if no such attr */
+
+ /*
+ * If there are a lot of result relations, use a hash table to speed the
+ * lookups. If there are not a lot, a simple linear search is faster.
+ *
+ * It's not clear where the threshold is, but try 64 for starters. In a
+ * debugging build, use a small threshold so that we get some test
+ * coverage of both code paths.
+ */
+#ifdef USE_ASSERT_CHECKING
+#define MT_NRELS_HASH 4
+#else
+#define MT_NRELS_HASH 64
+#endif
+ if (nrels >= MT_NRELS_HASH)
+ {
+ HASHCTL hash_ctl;
+
+ hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.entrysize = sizeof(MTTargetRelLookup);
+ hash_ctl.hcxt = CurrentMemoryContext;
+ mtstate->mt_resultOidHash =
+ hash_create("ModifyTable target hash",
+ nrels, &hash_ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ for (i = 0; i < nrels; i++)
{
- if (operation == CMD_INSERT)
- ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
- subplan->targetlist);
+ Oid hashkey;
+ MTTargetRelLookup *mtlookup;
+ bool found;
+
+ resultRelInfo = &mtstate->resultRelInfo[i];
+ hashkey = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ mtlookup = (MTTargetRelLookup *)
+ hash_search(mtstate->mt_resultOidHash, &hashkey,
+ HASH_ENTER, &found);
+ Assert(!found);
+ mtlookup->relationIndex = i;
}
}
+ else
+ mtstate->mt_resultOidHash = NULL;
/*
* Determine if the FDW supports batch insert and determine the batch
@@ -2804,7 +3037,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_INSERT)
{
resultRelInfo = mtstate->resultRelInfo;
- for (i = 0; i < nplans; i++)
+ for (i = 0; i < nrels; i++)
{
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
@@ -2853,7 +3086,7 @@ ExecEndModifyTable(ModifyTableState *node)
/*
* Allow any FDWs to shut down
*/
- for (i = 0; i < node->mt_nplans; i++)
+ for (i = 0; i < node->mt_nrels; i++)
{
ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
@@ -2893,10 +3126,9 @@ ExecEndModifyTable(ModifyTableState *node)
EvalPlanQualEnd(&node->mt_epqstate);
/*
- * shut down subplans
+ * shut down subplan
*/
- for (i = 0; i < node->mt_nplans; i++)
- ExecEndNode(node->mt_plans[i]);
+ ExecEndNode(outerPlanState(node));
}
void
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3a39fa41591..44c7fce20a1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -207,7 +207,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_SCALAR_FIELD(rootRelation);
COPY_SCALAR_FIELD(partColsUpdated);
COPY_NODE_FIELD(resultRelations);
- COPY_NODE_FIELD(plans);
+ COPY_NODE_FIELD(updateColnosLists);
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 38226530c6d..860e9a2a064 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4002,12 +4002,6 @@ planstate_tree_walker(PlanState *planstate,
/* special child plans */
switch (nodeTag(plan))
{
- case T_ModifyTable:
- if (planstate_walk_members(((ModifyTableState *) planstate)->mt_plans,
- ((ModifyTableState *) planstate)->mt_nplans,
- walker, context))
- return true;
- break;
case T_Append:
if (planstate_walk_members(((AppendState *) planstate)->appendplans,
((AppendState *) planstate)->as_nplans,
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 638b5381001..785465d8c4f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -408,7 +408,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_UINT_FIELD(rootRelation);
WRITE_BOOL_FIELD(partColsUpdated);
WRITE_NODE_FIELD(resultRelations);
- WRITE_NODE_FIELD(plans);
+ WRITE_NODE_FIELD(updateColnosLists);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
@@ -2138,14 +2138,14 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
_outPathInfo(str, (const Path *) node);
+ WRITE_NODE_FIELD(subpath);
WRITE_ENUM_FIELD(operation, CmdType);
WRITE_BOOL_FIELD(canSetTag);
WRITE_UINT_FIELD(nominalRelation);
WRITE_UINT_FIELD(rootRelation);
WRITE_BOOL_FIELD(partColsUpdated);
WRITE_NODE_FIELD(resultRelations);
- WRITE_NODE_FIELD(subpaths);
- WRITE_NODE_FIELD(subroots);
+ WRITE_NODE_FIELD(updateColnosLists);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
@@ -2261,7 +2261,10 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(right_join_clauses);
WRITE_NODE_FIELD(full_join_clauses);
WRITE_NODE_FIELD(join_info_list);
+ WRITE_BITMAPSET_FIELD(all_result_relids);
+ WRITE_BITMAPSET_FIELD(leaf_result_relids);
WRITE_NODE_FIELD(append_rel_list);
+ WRITE_NODE_FIELD(row_identity_vars);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
WRITE_NODE_FIELD(fkey_list);
@@ -2271,12 +2274,12 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(distinct_pathkeys);
WRITE_NODE_FIELD(sort_pathkeys);
WRITE_NODE_FIELD(processed_tlist);
+ WRITE_NODE_FIELD(update_colnos);
WRITE_NODE_FIELD(minmax_aggs);
WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
WRITE_UINT_FIELD(qual_security_level);
- WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
WRITE_BOOL_FIELD(hasJoinRTEs);
WRITE_BOOL_FIELD(hasLateralRTEs);
WRITE_BOOL_FIELD(hasHavingQual);
@@ -2577,6 +2580,17 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
}
static void
+_outRowIdentityVarInfo(StringInfo str, const RowIdentityVarInfo *node)
+{
+ WRITE_NODE_TYPE("ROWIDENTITYVARINFO");
+
+ WRITE_NODE_FIELD(rowidvar);
+ WRITE_INT_FIELD(rowidwidth);
+ WRITE_STRING_FIELD(rowidname);
+ WRITE_BITMAPSET_FIELD(rowidrels);
+}
+
+static void
_outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
{
WRITE_NODE_TYPE("PLACEHOLDERINFO");
@@ -4235,6 +4249,9 @@ outNode(StringInfo str, const void *obj)
case T_AppendRelInfo:
_outAppendRelInfo(str, obj);
break;
+ case T_RowIdentityVarInfo:
+ _outRowIdentityVarInfo(str, obj);
+ break;
case T_PlaceHolderInfo:
_outPlaceHolderInfo(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 4767e7092d7..a6e723a273a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1685,7 +1685,7 @@ _readModifyTable(void)
READ_UINT_FIELD(rootRelation);
READ_BOOL_FIELD(partColsUpdated);
READ_NODE_FIELD(resultRelations);
- READ_NODE_FIELD(plans);
+ READ_NODE_FIELD(updateColnosLists);
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 59f495d7439..f34399e3ec6 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1149,7 +1149,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
Var *parentvar = (Var *) lfirst(parentvars);
Node *childvar = (Node *) lfirst(childvars);
- if (IsA(parentvar, Var))
+ if (IsA(parentvar, Var) && parentvar->varno == parentRTindex)
{
int pndx = parentvar->varattno - rel->min_attr;
int32 child_width = 0;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 53b24e9e8c8..0e4e00eaf02 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -3398,7 +3398,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
* and pass them through to EvalPlanQual via a side channel; but for now,
* we just don't remove implied quals at all for target relations.
*/
- is_target_rel = (rel->relid == root->parse->resultRelation ||
+ is_target_rel = (bms_is_member(rel->relid, root->all_result_relids) ||
get_plan_rowmark(root->rowMarks, rel->relid) != NULL);
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 78ef068fb7b..a56936e0e90 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -298,11 +298,12 @@ static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
static LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam);
static Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
static ProjectSet *make_project_set(List *tlist, Plan *subplan);
-static ModifyTable *make_modifytable(PlannerInfo *root,
+static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
- List *resultRelations, List *subplans, List *subroots,
+ List *resultRelations,
+ List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
@@ -2292,12 +2293,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
/*
* During setrefs.c, we'll need the grouping_map to fix up the cols lists
* in GroupingFunc nodes. Save it for setrefs.c to use.
- *
- * This doesn't work if we're in an inheritance subtree (see notes in
- * create_modifytable_plan). Fortunately we can't be because there would
- * never be grouping in an UPDATE/DELETE; but let's Assert that.
*/
- Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->grouping_map == NULL);
root->grouping_map = grouping_map;
@@ -2459,12 +2455,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* with InitPlan output params. (We can't just do that locally in the
* MinMaxAgg node, because path nodes above here may have Agg references
* as well.) Save the mmaggregates list to tell setrefs.c to do that.
- *
- * This doesn't work if we're in an inheritance subtree (see notes in
- * create_modifytable_plan). Fortunately we can't be because there would
- * never be aggregates in an UPDATE/DELETE; but let's Assert that.
*/
- Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->minmax_aggs == NIL);
root->minmax_aggs = best_path->mmaggregates;
@@ -2681,46 +2672,24 @@ static ModifyTable *
create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
{
ModifyTable *plan;
- List *subplans = NIL;
- ListCell *subpaths,
- *subroots;
-
- /* Build the plan for each input path */
- forboth(subpaths, best_path->subpaths,
- subroots, best_path->subroots)
- {
- Path *subpath = (Path *) lfirst(subpaths);
- PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
- Plan *subplan;
-
- /*
- * In an inherited UPDATE/DELETE, reference the per-child modified
- * subroot while creating Plans from Paths for the child rel. This is
- * a kluge, but otherwise it's too hard to ensure that Plan creation
- * functions (particularly in FDWs) don't depend on the contents of
- * "root" matching what they saw at Path creation time. The main
- * downside is that creation functions for Plans that might appear
- * below a ModifyTable cannot expect to modify the contents of "root"
- * and have it "stick" for subsequent processing such as setrefs.c.
- * That's not great, but it seems better than the alternative.
- */
- subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST);
+ Path *subpath = best_path->subpath;
+ Plan *subplan;
- /* Transfer resname/resjunk labeling, too, to keep executor happy */
- apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist);
+ /* Subplan must produce exactly the specified tlist */
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
- subplans = lappend(subplans, subplan);
- }
+ /* Transfer resname/resjunk labeling, too, to keep executor happy */
+ apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
plan = make_modifytable(root,
+ subplan,
best_path->operation,
best_path->canSetTag,
best_path->nominalRelation,
best_path->rootRelation,
best_path->partColsUpdated,
best_path->resultRelations,
- subplans,
- best_path->subroots,
+ best_path->updateColnosLists,
best_path->withCheckOptionLists,
best_path->returningLists,
best_path->rowMarks,
@@ -6916,11 +6885,12 @@ make_project_set(List *tlist,
* Build a ModifyTable plan node
*/
static ModifyTable *
-make_modifytable(PlannerInfo *root,
+make_modifytable(PlannerInfo *root, Plan *subplan,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
- List *resultRelations, List *subplans, List *subroots,
+ List *resultRelations,
+ List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
@@ -6928,17 +6898,17 @@ make_modifytable(PlannerInfo *root,
List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc;
- ListCell *lc2;
int i;
- Assert(list_length(resultRelations) == list_length(subplans));
- Assert(list_length(resultRelations) == list_length(subroots));
+ Assert(operation == CMD_UPDATE ?
+ list_length(resultRelations) == list_length(updateColnosLists) :
+ updateColnosLists == NIL);
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
- node->plan.lefttree = NULL;
+ node->plan.lefttree = subplan;
node->plan.righttree = NULL;
node->plan.qual = NIL;
/* setrefs.c will fill in the targetlist, if needed */
@@ -6950,7 +6920,6 @@ make_modifytable(PlannerInfo *root,
node->rootRelation = rootRelation;
node->partColsUpdated = partColsUpdated;
node->resultRelations = resultRelations;
- node->plans = subplans;
if (!onconflict)
{
node->onConflictAction = ONCONFLICT_NONE;
@@ -6977,6 +6946,7 @@ make_modifytable(PlannerInfo *root,
node->exclRelRTI = onconflict->exclRelIndex;
node->exclRelTlist = onconflict->exclRelTlist;
}
+ node->updateColnosLists = updateColnosLists;
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
@@ -6989,10 +6959,9 @@ make_modifytable(PlannerInfo *root,
fdw_private_list = NIL;
direct_modify_plans = NULL;
i = 0;
- forboth(lc, resultRelations, lc2, subroots)
+ foreach(lc, resultRelations)
{
Index rti = lfirst_int(lc);
- PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
FdwRoutine *fdwroutine;
List *fdw_private;
bool direct_modify;
@@ -7004,16 +6973,16 @@ make_modifytable(PlannerInfo *root,
* so it's not a baserel; and there are also corner cases for
* updatable views where the target rel isn't a baserel.)
*/
- if (rti < subroot->simple_rel_array_size &&
- subroot->simple_rel_array[rti] != NULL)
+ if (rti < root->simple_rel_array_size &&
+ root->simple_rel_array[rti] != NULL)
{
- RelOptInfo *resultRel = subroot->simple_rel_array[rti];
+ RelOptInfo *resultRel = root->simple_rel_array[rti];
fdwroutine = resultRel->fdwroutine;
}
else
{
- RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
Assert(rte->rtekind == RTE_RELATION);
if (rte->relkind == RELKIND_FOREIGN_TABLE)
@@ -7036,16 +7005,16 @@ make_modifytable(PlannerInfo *root,
fdwroutine->IterateDirectModify != NULL &&
fdwroutine->EndDirectModify != NULL &&
withCheckOptionLists == NIL &&
- !has_row_triggers(subroot, rti, operation) &&
- !has_stored_generated_columns(subroot, rti))
- direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
+ !has_row_triggers(root, rti, operation) &&
+ !has_stored_generated_columns(root, rti))
+ direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);
if (!direct_modify &&
fdwroutine != NULL &&
fdwroutine->PlanForeignModify != NULL)
- fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
+ fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
else
fdw_private = NIL;
fdw_private_list = lappend(fdw_private_list, fdw_private);
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index e1a13e20c5a..273ac0acf7e 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -264,6 +264,13 @@ query_planner(PlannerInfo *root,
add_other_rels_to_query(root);
/*
+ * Distribute any UPDATE/DELETE row identity variables to the target
+ * relations. This can't be done till we've finished expansion of
+ * appendrels.
+ */
+ distribute_row_identity_vars(root);
+
+ /*
* Ready to do the primary planning.
*/
final_rel = make_one_rel(root, joinlist);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 0886bf4ae8f..898d7fcb0bd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -129,9 +129,7 @@ typedef struct
/* Local functions */
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
-static void inheritance_planner(PlannerInfo *root);
-static void grouping_planner(PlannerInfo *root, bool inheritance_update,
- double tuple_fraction);
+static void grouping_planner(PlannerInfo *root, double tuple_fraction);
static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
static List *remap_to_groupclause_idx(List *groupClause, List *gsets,
int *tleref_to_colnum_map);
@@ -615,15 +613,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->multiexpr_params = NIL;
root->eq_classes = NIL;
root->ec_merging_done = false;
+ root->all_result_relids =
+ parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
+ root->leaf_result_relids = NULL; /* we'll find out leaf-ness later */
root->append_rel_list = NIL;
+ root->row_identity_vars = NIL;
root->rowMarks = NIL;
memset(root->upper_rels, 0, sizeof(root->upper_rels));
memset(root->upper_targets, 0, sizeof(root->upper_targets));
root->processed_tlist = NIL;
+ root->update_colnos = NIL;
root->grouping_map = NULL;
root->minmax_aggs = NIL;
root->qual_security_level = 0;
- root->inhTargetKind = INHKIND_NONE;
root->hasPseudoConstantQuals = false;
root->hasAlternativeSubPlans = false;
root->hasRecursion = hasRecursion;
@@ -744,6 +746,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
}
/*
+ * If we have now verified that the query target relation is
+ * non-inheriting, mark it as a leaf target.
+ */
+ if (parse->resultRelation)
+ {
+ RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ if (!rte->inh)
+ root->leaf_result_relids =
+ bms_make_singleton(parse->resultRelation);
+ }
+
+ /*
* Preprocess RowMark information. We need to do this after subquery
* pullup, so that all base relations are present.
*/
@@ -999,14 +1014,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
remove_useless_result_rtes(root);
/*
- * Do the main planning. If we have an inherited target relation, that
- * needs special processing, else go straight to grouping_planner.
+ * Do the main planning.
*/
- if (parse->resultRelation &&
- rt_fetch(parse->resultRelation, parse->rtable)->inh)
- inheritance_planner(root);
- else
- grouping_planner(root, false, tuple_fraction);
+ grouping_planner(root, tuple_fraction);
/*
* Capture the set of outer-level param IDs we have access to, for use in
@@ -1180,621 +1190,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV);
}
-/*
- * inheritance_planner
- * Generate Paths in the case where the result relation is an
- * inheritance set.
- *
- * We have to handle this case differently from cases where a source relation
- * is an inheritance set. Source inheritance is expanded at the bottom of the
- * plan tree (see allpaths.c), but target inheritance has to be expanded at
- * the top. The reason is that for UPDATE, each target relation needs a
- * different targetlist matching its own column set. Fortunately,
- * the UPDATE/DELETE target can never be the nullable side of an outer join,
- * so it's OK to generate the plan this way.
- *
- * Returns nothing; the useful output is in the Paths we attach to
- * the (UPPERREL_FINAL, NULL) upperrel stored in *root.
- *
- * Note that we have not done set_cheapest() on the final rel; it's convenient
- * to leave this to the caller.
- */
-static void
-inheritance_planner(PlannerInfo *root)
-{
- Query *parse = root->parse;
- int top_parentRTindex = parse->resultRelation;
- List *select_rtable;
- List *select_appinfos;
- List *child_appinfos;
- List *old_child_rtis;
- List *new_child_rtis;
- Bitmapset *subqueryRTindexes;
- Index next_subquery_rti;
- int nominalRelation = -1;
- Index rootRelation = 0;
- List *final_rtable = NIL;
- List *final_rowmarks = NIL;
- List *final_appendrels = NIL;
- int save_rel_array_size = 0;
- RelOptInfo **save_rel_array = NULL;
- AppendRelInfo **save_append_rel_array = NULL;
- List *subpaths = NIL;
- List *subroots = NIL;
- List *resultRelations = NIL;
- List *withCheckOptionLists = NIL;
- List *returningLists = NIL;
- List *rowMarks;
- RelOptInfo *final_rel;
- ListCell *lc;
- ListCell *lc2;
- Index rti;
- RangeTblEntry *parent_rte;
- Bitmapset *parent_relids;
- Query **parent_parses;
-
- /* Should only get here for UPDATE or DELETE */
- Assert(parse->commandType == CMD_UPDATE ||
- parse->commandType == CMD_DELETE);
-
- /*
- * We generate a modified instance of the original Query for each target
- * relation, plan that, and put all the plans into a list that will be
- * controlled by a single ModifyTable node. All the instances share the
- * same rangetable, but each instance must have its own set of subquery
- * RTEs within the finished rangetable because (1) they are likely to get
- * scribbled on during planning, and (2) it's not inconceivable that
- * subqueries could get planned differently in different cases. We need
- * not create duplicate copies of other RTE kinds, in particular not the
- * target relations, because they don't have either of those issues. Not
- * having to duplicate the target relations is important because doing so
- * (1) would result in a rangetable of length O(N^2) for N targets, with
- * at least O(N^3) work expended here; and (2) would greatly complicate
- * management of the rowMarks list.
- *
- * To begin with, generate a bitmapset of the relids of the subquery RTEs.
- */
- subqueryRTindexes = NULL;
- rti = 1;
- foreach(lc, parse->rtable)
- {
- RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
- if (rte->rtekind == RTE_SUBQUERY)
- subqueryRTindexes = bms_add_member(subqueryRTindexes, rti);
- rti++;
- }
-
- /*
- * If the parent RTE is a partitioned table, we should use that as the
- * nominal target relation, because the RTEs added for partitioned tables
- * (including the root parent) as child members of the inheritance set do
- * not appear anywhere else in the plan, so the confusion explained below
- * for non-partitioning inheritance cases is not possible.
- */
- parent_rte = rt_fetch(top_parentRTindex, parse->rtable);
- Assert(parent_rte->inh);
- if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
- {
- nominalRelation = top_parentRTindex;
- rootRelation = top_parentRTindex;
- }
-
- /*
- * Before generating the real per-child-relation plans, do a cycle of
- * planning as though the query were a SELECT. The objective here is to
- * find out which child relations need to be processed, using the same
- * expansion and pruning logic as for a SELECT. We'll then pull out the
- * RangeTblEntry-s generated for the child rels, and make use of the
- * AppendRelInfo entries for them to guide the real planning. (This is
- * rather inefficient; we could perhaps stop short of making a full Path
- * tree. But this whole function is inefficient and slated for
- * destruction, so let's not contort query_planner for that.)
- */
- {
- PlannerInfo *subroot;
-
- /*
- * Flat-copy the PlannerInfo to prevent modification of the original.
- */
- subroot = makeNode(PlannerInfo);
- memcpy(subroot, root, sizeof(PlannerInfo));
-
- /*
- * Make a deep copy of the parsetree for this planning cycle to mess
- * around with, and change it to look like a SELECT. (Hack alert: the
- * target RTE still has updatedCols set if this is an UPDATE, so that
- * expand_partitioned_rtentry will correctly update
- * subroot->partColsUpdated.)
- */
- subroot->parse = copyObject(root->parse);
-
- subroot->parse->commandType = CMD_SELECT;
- subroot->parse->resultRelation = 0;
-
- /*
- * Ensure the subroot has its own copy of the original
- * append_rel_list, since it'll be scribbled on. (Note that at this
- * point, the list only contains AppendRelInfos for flattened UNION
- * ALL subqueries.)
- */
- subroot->append_rel_list = copyObject(root->append_rel_list);
-
- /*
- * Better make a private copy of the rowMarks, too.
- */
- subroot->rowMarks = copyObject(root->rowMarks);
-
- /* There shouldn't be any OJ info to translate, as yet */
- Assert(subroot->join_info_list == NIL);
- /* and we haven't created PlaceHolderInfos, either */
- Assert(subroot->placeholder_list == NIL);
-
- /* Generate Path(s) for accessing this result relation */
- grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
- /* Extract the info we need. */
- select_rtable = subroot->parse->rtable;
- select_appinfos = subroot->append_rel_list;
-
- /*
- * We need to propagate partColsUpdated back, too. (The later
- * planning cycles will not set this because they won't run
- * expand_partitioned_rtentry for the UPDATE target.)
- */
- root->partColsUpdated = subroot->partColsUpdated;
- }
-
- /*----------
- * Since only one rangetable can exist in the final plan, we need to make
- * sure that it contains all the RTEs needed for any child plan. This is
- * complicated by the need to use separate subquery RTEs for each child.
- * We arrange the final rtable as follows:
- * 1. All original rtable entries (with their original RT indexes).
- * 2. All the relation RTEs generated for children of the target table.
- * 3. Subquery RTEs for children after the first. We need N * (K - 1)
- * RT slots for this, if there are N subqueries and K child tables.
- * 4. Additional RTEs generated during the child planning runs, such as
- * children of inheritable RTEs other than the target table.
- * We assume that each child planning run will create an identical set
- * of type-4 RTEs.
- *
- * So the next thing to do is append the type-2 RTEs (the target table's
- * children) to the original rtable. We look through select_appinfos
- * to find them.
- *
- * To identify which AppendRelInfos are relevant as we thumb through
- * select_appinfos, we need to look for both direct and indirect children
- * of top_parentRTindex, so we use a bitmap of known parent relids.
- * expand_inherited_rtentry() always processes a parent before any of that
- * parent's children, so we should see an intermediate parent before its
- * children.
- *----------
- */
- child_appinfos = NIL;
- old_child_rtis = NIL;
- new_child_rtis = NIL;
- parent_relids = bms_make_singleton(top_parentRTindex);
- foreach(lc, select_appinfos)
- {
- AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
- RangeTblEntry *child_rte;
-
- /* append_rel_list contains all append rels; ignore others */
- if (!bms_is_member(appinfo->parent_relid, parent_relids))
- continue;
-
- /* remember relevant AppendRelInfos for use below */
- child_appinfos = lappend(child_appinfos, appinfo);
-
- /* extract RTE for this child rel */
- child_rte = rt_fetch(appinfo->child_relid, select_rtable);
-
- /* and append it to the original rtable */
- parse->rtable = lappend(parse->rtable, child_rte);
-
- /* remember child's index in the SELECT rtable */
- old_child_rtis = lappend_int(old_child_rtis, appinfo->child_relid);
-
- /* and its new index in the final rtable */
- new_child_rtis = lappend_int(new_child_rtis, list_length(parse->rtable));
-
- /* if child is itself partitioned, update parent_relids */
- if (child_rte->inh)
- {
- Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
- parent_relids = bms_add_member(parent_relids, appinfo->child_relid);
- }
- }
-
- /*
- * It's possible that the RTIs we just assigned for the child rels in the
- * final rtable are different from what they were in the SELECT query.
- * Adjust the AppendRelInfos so that they will correctly map RT indexes to
- * the final indexes. We can do this left-to-right since no child rel's
- * final RT index could be greater than what it had in the SELECT query.
- */
- forboth(lc, old_child_rtis, lc2, new_child_rtis)
- {
- int old_child_rti = lfirst_int(lc);
- int new_child_rti = lfirst_int(lc2);
-
- if (old_child_rti == new_child_rti)
- continue; /* nothing to do */
-
- Assert(old_child_rti > new_child_rti);
-
- ChangeVarNodes((Node *) child_appinfos,
- old_child_rti, new_child_rti, 0);
- }
-
- /*
- * Now set up rangetable entries for subqueries for additional children
- * (the first child will just use the original ones). These all have to
- * look more or less real, or EXPLAIN will get unhappy; so we just make
- * them all clones of the original subqueries.
- */
- next_subquery_rti = list_length(parse->rtable) + 1;
- if (subqueryRTindexes != NULL)
- {
- int n_children = list_length(child_appinfos);
-
- while (n_children-- > 1)
- {
- int oldrti = -1;
-
- while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
- {
- RangeTblEntry *subqrte;
-
- subqrte = rt_fetch(oldrti, parse->rtable);
- parse->rtable = lappend(parse->rtable, copyObject(subqrte));
- }
- }
- }
-
- /*
- * The query for each child is obtained by translating the query for its
- * immediate parent, since the AppendRelInfo data we have shows deltas
- * between parents and children. We use the parent_parses array to
- * remember the appropriate query trees. This is indexed by parent relid.
- * Since the maximum number of parents is limited by the number of RTEs in
- * the SELECT query, we use that number to allocate the array. An extra
- * entry is needed since relids start from 1.
- */
- parent_parses = (Query **) palloc0((list_length(select_rtable) + 1) *
- sizeof(Query *));
- parent_parses[top_parentRTindex] = parse;
-
- /*
- * And now we can get on with generating a plan for each child table.
- */
- foreach(lc, child_appinfos)
- {
- AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
- Index this_subquery_rti = next_subquery_rti;
- Query *parent_parse;
- PlannerInfo *subroot;
- RangeTblEntry *child_rte;
- RelOptInfo *sub_final_rel;
- Path *subpath;
-
- /*
- * expand_inherited_rtentry() always processes a parent before any of
- * that parent's children, so the parent query for this relation
- * should already be available.
- */
- parent_parse = parent_parses[appinfo->parent_relid];
- Assert(parent_parse != NULL);
-
- /*
- * We need a working copy of the PlannerInfo so that we can control
- * propagation of information back to the main copy.
- */
- subroot = makeNode(PlannerInfo);
- memcpy(subroot, root, sizeof(PlannerInfo));
-
- /*
- * Generate modified query with this rel as target. We first apply
- * adjust_appendrel_attrs, which copies the Query and changes
- * references to the parent RTE to refer to the current child RTE,
- * then fool around with subquery RTEs.
- */
- subroot->parse = (Query *)
- adjust_appendrel_attrs(subroot,
- (Node *) parent_parse,
- 1, &appinfo);
-
- /*
- * If there are securityQuals attached to the parent, move them to the
- * child rel (they've already been transformed properly for that).
- */
- parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable);
- child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
- child_rte->securityQuals = parent_rte->securityQuals;
- parent_rte->securityQuals = NIL;
-
- /*
- * HACK: setting this to a value other than INHKIND_NONE signals to
- * relation_excluded_by_constraints() to treat the result relation as
- * being an appendrel member.
- */
- subroot->inhTargetKind =
- (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
-
- /*
- * If this child is further partitioned, remember it as a parent.
- * Since a partitioned table does not have any data, we don't need to
- * create a plan for it, and we can stop processing it here. We do,
- * however, need to remember its modified PlannerInfo for use when
- * processing its children, since we'll update their varnos based on
- * the delta from immediate parent to child, not from top to child.
- *
- * Note: a very non-obvious point is that we have not yet added
- * duplicate subquery RTEs to the subroot's rtable. We mustn't,
- * because then its children would have two sets of duplicates,
- * confusing matters.
- */
- if (child_rte->inh)
- {
- Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
- parent_parses[appinfo->child_relid] = subroot->parse;
- continue;
- }
-
- /*
- * Set the nominal target relation of the ModifyTable node if not
- * already done. If the target is a partitioned table, we already set
- * nominalRelation to refer to the partition root, above. For
- * non-partitioned inheritance cases, we'll use the first child
- * relation (even if it's excluded) as the nominal target relation.
- * Because of the way expand_inherited_rtentry works, that should be
- * the RTE representing the parent table in its role as a simple
- * member of the inheritance set.
- *
- * It would be logically cleaner to *always* use the inheritance
- * parent RTE as the nominal relation; but that RTE is not otherwise
- * referenced in the plan in the non-partitioned inheritance case.
- * Instead the duplicate child RTE created by expand_inherited_rtentry
- * is used elsewhere in the plan, so using the original parent RTE
- * would give rise to confusing use of multiple aliases in EXPLAIN
- * output for what the user will think is the "same" table. OTOH,
- * it's not a problem in the partitioned inheritance case, because
- * there is no duplicate RTE for the parent.
- */
- if (nominalRelation < 0)
- nominalRelation = appinfo->child_relid;
-
- /*
- * As above, each child plan run needs its own append_rel_list and
- * rowmarks, which should start out as pristine copies of the
- * originals. There can't be any references to UPDATE/DELETE target
- * rels in them; but there could be subquery references, which we'll
- * fix up in a moment.
- */
- subroot->append_rel_list = copyObject(root->append_rel_list);
- subroot->rowMarks = copyObject(root->rowMarks);
-
- /*
- * If this isn't the first child Query, adjust Vars and jointree
- * entries to reference the appropriate set of subquery RTEs.
- */
- if (final_rtable != NIL && subqueryRTindexes != NULL)
- {
- int oldrti = -1;
-
- while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
- {
- Index newrti = next_subquery_rti++;
-
- ChangeVarNodes((Node *) subroot->parse, oldrti, newrti, 0);
- ChangeVarNodes((Node *) subroot->append_rel_list,
- oldrti, newrti, 0);
- ChangeVarNodes((Node *) subroot->rowMarks, oldrti, newrti, 0);
- }
- }
-
- /* There shouldn't be any OJ info to translate, as yet */
- Assert(subroot->join_info_list == NIL);
- /* and we haven't created PlaceHolderInfos, either */
- Assert(subroot->placeholder_list == NIL);
-
- /* Generate Path(s) for accessing this result relation */
- grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
- /*
- * Select cheapest path in case there's more than one. We always run
- * modification queries to conclusion, so we care only for the
- * cheapest-total path.
- */
- sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
- set_cheapest(sub_final_rel);
- subpath = sub_final_rel->cheapest_total_path;
-
- /*
- * If this child rel was excluded by constraint exclusion, exclude it
- * from the result plan.
- */
- if (IS_DUMMY_REL(sub_final_rel))
- continue;
-
- /*
- * If this is the first non-excluded child, its post-planning rtable
- * becomes the initial contents of final_rtable; otherwise, copy its
- * modified subquery RTEs into final_rtable, to ensure we have sane
- * copies of those. Also save the first non-excluded child's version
- * of the rowmarks list; we assume all children will end up with
- * equivalent versions of that. Likewise for append_rel_list.
- */
- if (final_rtable == NIL)
- {
- final_rtable = subroot->parse->rtable;
- final_rowmarks = subroot->rowMarks;
- final_appendrels = subroot->append_rel_list;
- }
- else
- {
- Assert(list_length(final_rtable) ==
- list_length(subroot->parse->rtable));
- if (subqueryRTindexes != NULL)
- {
- int oldrti = -1;
-
- while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
- {
- Index newrti = this_subquery_rti++;
- RangeTblEntry *subqrte;
- ListCell *newrticell;
-
- subqrte = rt_fetch(newrti, subroot->parse->rtable);
- newrticell = list_nth_cell(final_rtable, newrti - 1);
- lfirst(newrticell) = subqrte;
- }
- }
- }
-
- /*
- * We need to collect all the RelOptInfos from all child plans into
- * the main PlannerInfo, since setrefs.c will need them. We use the
- * last child's simple_rel_array, so we have to propagate forward the
- * RelOptInfos that were already built in previous children.
- */
- Assert(subroot->simple_rel_array_size >= save_rel_array_size);
- for (rti = 1; rti < save_rel_array_size; rti++)
- {
- RelOptInfo *brel = save_rel_array[rti];
-
- if (brel)
- subroot->simple_rel_array[rti] = brel;
- }
- save_rel_array_size = subroot->simple_rel_array_size;
- save_rel_array = subroot->simple_rel_array;
- save_append_rel_array = subroot->append_rel_array;
-
- /*
- * Make sure any initplans from this rel get into the outer list. Note
- * we're effectively assuming all children generate the same
- * init_plans.
- */
- root->init_plans = subroot->init_plans;
-
- /* Build list of sub-paths */
- subpaths = lappend(subpaths, subpath);
-
- /* Build list of modified subroots, too */
- subroots = lappend(subroots, subroot);
-
- /* Build list of target-relation RT indexes */
- resultRelations = lappend_int(resultRelations, appinfo->child_relid);
-
- /* Build lists of per-relation WCO and RETURNING targetlists */
- if (parse->withCheckOptions)
- withCheckOptionLists = lappend(withCheckOptionLists,
- subroot->parse->withCheckOptions);
- if (parse->returningList)
- returningLists = lappend(returningLists,
- subroot->parse->returningList);
-
- Assert(!parse->onConflict);
- }
-
- /* Result path must go into outer query's FINAL upperrel */
- final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
-
- /*
- * We don't currently worry about setting final_rel's consider_parallel
- * flag in this case, nor about allowing FDWs or create_upper_paths_hook
- * to get control here.
- */
-
- if (subpaths == NIL)
- {
- /*
- * We managed to exclude every child rel, so generate a dummy path
- * representing the empty set. Although it's clear that no data will
- * be updated or deleted, we will still need to have a ModifyTable
- * node so that any statement triggers are executed. (This could be
- * cleaner if we fixed nodeModifyTable.c to support zero child nodes,
- * but that probably wouldn't be a net win.)
- */
- Path *dummy_path;
-
- /* tlist processing never got done, either */
- root->processed_tlist = preprocess_targetlist(root);
- final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
-
- /* Make a dummy path, cf set_dummy_rel_pathlist() */
- dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
- NIL, NULL, 0, false,
- -1);
-
- /* These lists must be nonempty to make a valid ModifyTable node */
- subpaths = list_make1(dummy_path);
- subroots = list_make1(root);
- resultRelations = list_make1_int(parse->resultRelation);
- if (parse->withCheckOptions)
- withCheckOptionLists = list_make1(parse->withCheckOptions);
- if (parse->returningList)
- returningLists = list_make1(parse->returningList);
- /* Disable tuple routing, too, just to be safe */
- root->partColsUpdated = false;
- }
- else
- {
- /*
- * Put back the final adjusted rtable into the original copy of the
- * Query. (We mustn't do this if we found no non-excluded children,
- * since we never saved an adjusted rtable at all.)
- */
- parse->rtable = final_rtable;
- root->simple_rel_array_size = save_rel_array_size;
- root->simple_rel_array = save_rel_array;
- root->append_rel_array = save_append_rel_array;
-
- /* Must reconstruct original's simple_rte_array, too */
- root->simple_rte_array = (RangeTblEntry **)
- palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
- rti = 1;
- foreach(lc, final_rtable)
- {
- RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
- root->simple_rte_array[rti++] = rte;
- }
-
- /* Put back adjusted rowmarks and appendrels, too */
- root->rowMarks = final_rowmarks;
- root->append_rel_list = final_appendrels;
- }
-
- /*
- * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
- * have dealt with fetching non-locked marked rows, else we need to have
- * ModifyTable do that.
- */
- if (parse->rowMarks)
- rowMarks = NIL;
- else
- rowMarks = root->rowMarks;
-
- /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
- add_path(final_rel, (Path *)
- create_modifytable_path(root, final_rel,
- parse->commandType,
- parse->canSetTag,
- nominalRelation,
- rootRelation,
- root->partColsUpdated,
- resultRelations,
- subpaths,
- subroots,
- withCheckOptionLists,
- returningLists,
- rowMarks,
- NULL,
- assign_special_exec_param(root)));
-}
-
/*--------------------
* grouping_planner
* Perform planning steps related to grouping, aggregation, etc.
@@ -1802,11 +1197,6 @@ inheritance_planner(PlannerInfo *root)
* This function adds all required top-level processing to the scan/join
* Path(s) produced by query_planner.
*
- * If inheritance_update is true, we're being called from inheritance_planner
- * and should not include a ModifyTable step in the resulting Path(s).
- * (inheritance_planner will create a single ModifyTable node covering all the
- * target tables.)
- *
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as follows:
* 0: expect all tuples to be retrieved (normal case)
@@ -1824,8 +1214,7 @@ inheritance_planner(PlannerInfo *root)
*--------------------
*/
static void
-grouping_planner(PlannerInfo *root, bool inheritance_update,
- double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction)
{
Query *parse = root->parse;
int64 offset_est = 0;
@@ -1969,7 +1358,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
* that we can transfer its decoration (resnames etc) to the topmost
* tlist of the finished Plan. This is kept in processed_tlist.
*/
- root->processed_tlist = preprocess_targetlist(root);
+ preprocess_targetlist(root);
/*
* Mark all the aggregates with resolved aggtranstypes, and detect
@@ -2307,16 +1696,117 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
}
/*
- * If this is an INSERT/UPDATE/DELETE, and we're not being called from
- * inheritance_planner, add the ModifyTable node.
+ * If this is an INSERT/UPDATE/DELETE, add the ModifyTable node.
*/
- if (parse->commandType != CMD_SELECT && !inheritance_update)
+ if (parse->commandType != CMD_SELECT)
{
Index rootRelation;
- List *withCheckOptionLists;
- List *returningLists;
+ List *resultRelations = NIL;
+ List *updateColnosLists = NIL;
+ List *withCheckOptionLists = NIL;
+ List *returningLists = NIL;
List *rowMarks;
+ if (bms_membership(root->all_result_relids) == BMS_MULTIPLE)
+ {
+ /* Inherited UPDATE/DELETE */
+ RelOptInfo *top_result_rel = find_base_rel(root,
+ parse->resultRelation);
+ int resultRelation = -1;
+
+ /* Add only leaf children to ModifyTable. */
+ while ((resultRelation = bms_next_member(root->leaf_result_relids,
+ resultRelation)) >= 0)
+ {
+ RelOptInfo *this_result_rel = find_base_rel(root,
+ resultRelation);
+
+ /*
+ * Also exclude any leaf rels that have turned dummy since
+ * being added to the list, for example, by being excluded
+ * by constraint exclusion.
+ */
+ if (IS_DUMMY_REL(this_result_rel))
+ continue;
+
+ /* Build per-target-rel lists needed by ModifyTable */
+ resultRelations = lappend_int(resultRelations,
+ resultRelation);
+ if (parse->commandType == CMD_UPDATE)
+ {
+ List *update_colnos = root->update_colnos;
+
+ if (this_result_rel != top_result_rel)
+ update_colnos =
+ adjust_inherited_attnums_multilevel(root,
+ update_colnos,
+ this_result_rel->relid,
+ top_result_rel->relid);
+ updateColnosLists = lappend(updateColnosLists,
+ update_colnos);
+ }
+ if (parse->withCheckOptions)
+ {
+ List *withCheckOptions = parse->withCheckOptions;
+
+ if (this_result_rel != top_result_rel)
+ withCheckOptions = (List *)
+ adjust_appendrel_attrs_multilevel(root,
+ (Node *) withCheckOptions,
+ this_result_rel->relids,
+ top_result_rel->relids);
+ withCheckOptionLists = lappend(withCheckOptionLists,
+ withCheckOptions);
+ }
+ if (parse->returningList)
+ {
+ List *returningList = parse->returningList;
+
+ if (this_result_rel != top_result_rel)
+ returningList = (List *)
+ adjust_appendrel_attrs_multilevel(root,
+ (Node *) returningList,
+ this_result_rel->relids,
+ top_result_rel->relids);
+ returningLists = lappend(returningLists,
+ returningList);
+ }
+ }
+
+ if (resultRelations == NIL)
+ {
+ /*
+ * We managed to exclude every child rel, so generate a
+ * dummy one-relation plan using info for the top target
+ * rel (even though that may not be a leaf target).
+ * Although it's clear that no data will be updated or
+ * deleted, we still need to have a ModifyTable node so
+ * that any statement triggers will be executed. (This
+ * could be cleaner if we fixed nodeModifyTable.c to allow
+ * zero target relations, but that probably wouldn't be a
+ * net win.)
+ */
+ resultRelations = list_make1_int(parse->resultRelation);
+ if (parse->commandType == CMD_UPDATE)
+ updateColnosLists = list_make1(root->update_colnos);
+ if (parse->withCheckOptions)
+ withCheckOptionLists = list_make1(parse->withCheckOptions);
+ if (parse->returningList)
+ returningLists = list_make1(parse->returningList);
+ }
+ }
+ else
+ {
+ /* Single-relation INSERT/UPDATE/DELETE. */
+ resultRelations = list_make1_int(parse->resultRelation);
+ if (parse->commandType == CMD_UPDATE)
+ updateColnosLists = list_make1(root->update_colnos);
+ if (parse->withCheckOptions)
+ withCheckOptionLists = list_make1(parse->withCheckOptions);
+ if (parse->returningList)
+ returningLists = list_make1(parse->returningList);
+ }
+
/*
* If target is a partition root table, we need to mark the
* ModifyTable node appropriately for that.
@@ -2328,20 +1818,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
rootRelation = 0;
/*
- * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
- * needed.
- */
- if (parse->withCheckOptions)
- withCheckOptionLists = list_make1(parse->withCheckOptions);
- else
- withCheckOptionLists = NIL;
-
- if (parse->returningList)
- returningLists = list_make1(parse->returningList);
- else
- returningLists = NIL;
-
- /*
* If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node
* will have dealt with fetching non-locked marked rows, else we
* need to have ModifyTable do that.
@@ -2353,14 +1829,14 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
path = (Path *)
create_modifytable_path(root, final_rel,
+ path,
parse->commandType,
parse->canSetTag,
parse->resultRelation,
rootRelation,
- false,
- list_make1_int(parse->resultRelation),
- list_make1(path),
- list_make1(root),
+ root->partColsUpdated,
+ resultRelations,
+ updateColnosLists,
withCheckOptionLists,
returningLists,
rowMarks,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 018af8f1eb8..4a25431bec2 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -868,6 +868,29 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
set_upper_references(root, plan, rtoffset);
else
{
+ /*
+ * The tlist of a childless Result could contain
+ * unresolved ROWID_VAR Vars, in case it's representing a
+ * target relation which is completely empty because of
+ * constraint exclusion. Replace any such Vars by null
+ * constants, as though they'd been resolved for a leaf
+ * scan node that doesn't support them. We could have
+ * fix_scan_expr do this, but since the case is only
+ * expected to occur here, it seems safer to special-case
+ * it here and keep the assertions that ROWID_VARs
+ * shouldn't be seen by fix_scan_expr.
+ */
+ foreach(l, splan->plan.targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Var *var = (Var *) tle->expr;
+
+ if (var && IsA(var, Var) && var->varno == ROWID_VAR)
+ tle->expr = (Expr *) makeNullConst(var->vartype,
+ var->vartypmod,
+ var->varcollid);
+ }
+
splan->plan.targetlist =
fix_scan_list(root, splan->plan.targetlist,
rtoffset, NUM_EXEC_TLIST(plan));
@@ -897,23 +920,20 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
if (splan->returningLists)
{
List *newRL = NIL;
+ Plan *subplan = outerPlan(splan);
ListCell *lcrl,
- *lcrr,
- *lcp;
+ *lcrr;
/*
- * Pass each per-subplan returningList through
+ * Pass each per-resultrel returningList through
* set_returning_clause_references().
*/
Assert(list_length(splan->returningLists) == list_length(splan->resultRelations));
- Assert(list_length(splan->returningLists) == list_length(splan->plans));
- forthree(lcrl, splan->returningLists,
- lcrr, splan->resultRelations,
- lcp, splan->plans)
+ forboth(lcrl, splan->returningLists,
+ lcrr, splan->resultRelations)
{
List *rlist = (List *) lfirst(lcrl);
Index resultrel = lfirst_int(lcrr);
- Plan *subplan = (Plan *) lfirst(lcp);
rlist = set_returning_clause_references(root,
rlist,
@@ -983,12 +1003,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
- foreach(l, splan->plans)
- {
- lfirst(l) = set_plan_refs(root,
- (Plan *) lfirst(l),
- rtoffset);
- }
/*
* Append this ModifyTable node's final result relation RT
@@ -1792,6 +1806,13 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
* choosing the best implementation for AlternativeSubPlans,
* looking up operator opcode info for OpExpr and related nodes,
* and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ *
+ * 'node': the expression to be modified
+ * 'rtoffset': how much to increment varnos by
+ * 'num_exec': estimated number of executions of expression
+ *
+ * The expression tree is either copied-and-modified, or modified in-place
+ * if that seems safe.
*/
static Node *
fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
@@ -1840,11 +1861,12 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
Assert(var->varlevelsup == 0);
/*
- * We should not see any Vars marked INNER_VAR or OUTER_VAR. But an
- * indexqual expression could contain INDEX_VAR Vars.
+ * We should not see Vars marked INNER_VAR, OUTER_VAR, or ROWID_VAR.
+ * But an indexqual expression could contain INDEX_VAR Vars.
*/
Assert(var->varno != INNER_VAR);
Assert(var->varno != OUTER_VAR);
+ Assert(var->varno != ROWID_VAR);
if (!IS_SPECIAL_VARNO(var->varno))
var->varno += context->rtoffset;
if (var->varnosyn > 0)
@@ -1907,6 +1929,7 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
{
if (node == NULL)
return false;
+ Assert(!(IsA(node, Var) && ((Var *) node)->varno == ROWID_VAR));
Assert(!IsA(node, PlaceHolderVar));
Assert(!IsA(node, AlternativeSubPlan));
fix_expr_common(context->root, node);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 9bb84c4c301..15b9453975e 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2535,7 +2535,6 @@ finalize_plan(PlannerInfo *root, Plan *plan,
case T_ModifyTable:
{
ModifyTable *mtplan = (ModifyTable *) plan;
- ListCell *l;
/* Force descendant scan nodes to reference epqParam */
locally_added_param = mtplan->epqParam;
@@ -2550,16 +2549,6 @@ finalize_plan(PlannerInfo *root, Plan *plan,
finalize_primnode((Node *) mtplan->onConflictWhere,
&context);
/* exclRelTlist contains only Vars, doesn't need examination */
- foreach(l, mtplan->plans)
- {
- context.paramids =
- bms_add_members(context.paramids,
- finalize_plan(root,
- (Plan *) lfirst(l),
- gather_param,
- valid_params,
- scan_params));
- }
}
break;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index d961592e015..62a16687963 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -920,15 +920,18 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->multiexpr_params = NIL;
subroot->eq_classes = NIL;
subroot->ec_merging_done = false;
+ subroot->all_result_relids = NULL;
+ subroot->leaf_result_relids = NULL;
subroot->append_rel_list = NIL;
+ subroot->row_identity_vars = NIL;
subroot->rowMarks = NIL;
memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
memset(subroot->upper_targets, 0, sizeof(subroot->upper_targets));
subroot->processed_tlist = NIL;
+ subroot->update_colnos = NIL;
subroot->grouping_map = NULL;
subroot->minmax_aggs = NIL;
subroot->qual_security_level = 0;
- subroot->inhTargetKind = INHKIND_NONE;
subroot->hasRecursion = false;
subroot->wt_param_id = -1;
subroot->non_recursive_path = NULL;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 23f9f861f44..363132185d0 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -3,30 +3,26 @@
* preptlist.c
* Routines to preprocess the parse tree target list
*
- * For INSERT and UPDATE queries, the targetlist must contain an entry for
- * each attribute of the target relation in the correct order. For UPDATE and
- * DELETE queries, it must also contain junk tlist entries needed to allow the
- * executor to identify the rows to be updated or deleted. For all query
- * types, we may need to add junk tlist entries for Vars used in the RETURNING
- * list and row ID information needed for SELECT FOR UPDATE locking and/or
- * EvalPlanQual checking.
+ * For an INSERT, the targetlist must contain an entry for each attribute of
+ * the target relation in the correct order.
+ *
+ * For an UPDATE, the targetlist just contains the expressions for the new
+ * column values.
+ *
+ * For UPDATE and DELETE queries, the targetlist must also contain "junk"
+ * tlist entries needed to allow the executor to identify the rows to be
+ * updated or deleted; for example, the ctid of a heap row. (The planner
+ * adds these; they're not in what we receive from the planner/rewriter.)
+ *
+ * For all query types, there can be additional junk tlist entries, such as
+ * sort keys, Vars needed for a RETURNING list, and row ID information needed
+ * for SELECT FOR UPDATE locking and/or EvalPlanQual checking.
*
* The query rewrite phase also does preprocessing of the targetlist (see
* rewriteTargetListIU). The division of labor between here and there is
- * partially historical, but it's not entirely arbitrary. In particular,
- * consider an UPDATE across an inheritance tree. What rewriteTargetListIU
- * does need be done only once (because it depends only on the properties of
- * the parent relation). What's done here has to be done over again for each
- * child relation, because it depends on the properties of the child, which
- * might be of a different relation type, or have more columns and/or a
- * different column order than the parent.
- *
- * The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column
- * position, which expand_targetlist depends on, violates the above comment
- * because the sorting is only valid for the parent relation. In inherited
- * UPDATE cases, adjust_inherited_tlist runs in between to take care of fixing
- * the tlists for child tables to keep expand_targetlist happy. We do it like
- * that because it's faster in typical non-inherited cases.
+ * partially historical, but it's not entirely arbitrary. The stuff done
+ * here is closely connected to physical access to tables, whereas the
+ * rewriter's work is more concerned with SQL semantics.
*
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
@@ -40,18 +36,17 @@
#include "postgres.h"
-#include "access/sysattr.h"
#include "access/table.h"
-#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
+#include "optimizer/appendinfo.h"
#include "optimizer/optimizer.h"
#include "optimizer/prep.h"
#include "optimizer/tlist.h"
#include "parser/parse_coerce.h"
#include "parser/parsetree.h"
-#include "rewrite/rewriteHandler.h"
#include "utils/rel.h"
+static List *extract_update_colnos(List *tlist);
static List *expand_targetlist(List *tlist, int command_type,
Index result_relation, Relation rel);
@@ -60,12 +55,15 @@ static List *expand_targetlist(List *tlist, int command_type,
* preprocess_targetlist
* Driver for preprocessing the parse tree targetlist.
*
- * Returns the new targetlist.
+ * The preprocessed targetlist is returned in root->processed_tlist.
+ * Also, if this is an UPDATE, we return a list of target column numbers
+ * in root->update_colnos. (Resnos in processed_tlist will be consecutive,
+ * so do not look at that to find out which columns are targets!)
*
* As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist
* is also preprocessed (and updated in-place).
*/
-List *
+void
preprocess_targetlist(PlannerInfo *root)
{
Query *parse = root->parse;
@@ -99,23 +97,38 @@ preprocess_targetlist(PlannerInfo *root)
Assert(command_type == CMD_SELECT);
/*
- * For UPDATE/DELETE, add any junk column(s) needed to allow the executor
- * to identify the rows to be updated or deleted. Note that this step
- * scribbles on parse->targetList, which is not very desirable, but we
- * keep it that way to avoid changing APIs used by FDWs.
- */
- if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
- rewriteTargetListUD(parse, target_rte, target_relation);
-
- /*
- * for heap_form_tuple to work, the targetlist must match the exact order
- * of the attributes. We also need to fill in any missing attributes. -ay
- * 10/94
+ * In an INSERT, the executor expects the targetlist to match the exact
+ * order of the target table's attributes, including entries for
+ * attributes not mentioned in the source query.
+ *
+ * In an UPDATE, we don't rearrange the tlist order, but we need to make a
+ * separate list of the target attribute numbers, in tlist order, and then
+ * renumber the processed_tlist entries to be consecutive.
*/
tlist = parse->targetList;
- if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ if (command_type == CMD_INSERT)
tlist = expand_targetlist(tlist, command_type,
result_relation, target_relation);
+ else if (command_type == CMD_UPDATE)
+ root->update_colnos = extract_update_colnos(tlist);
+
+ /*
+ * For non-inherited UPDATE/DELETE, register any junk column(s) needed to
+ * allow the executor to identify the rows to be updated or deleted. In
+ * the inheritance case, we do nothing now, leaving this to be dealt with
+ * when expand_inherited_rtentry() makes the leaf target relations. (But
+ * there might not be any leaf target relations, in which case we must do
+ * this in distribute_row_identity_vars().)
+ */
+ if ((command_type == CMD_UPDATE || command_type == CMD_DELETE) &&
+ !target_rte->inh)
+ {
+ /* row-identity logic expects to add stuff to processed_tlist */
+ root->processed_tlist = tlist;
+ add_row_identity_columns(root, result_relation,
+ target_rte, target_relation);
+ tlist = root->processed_tlist;
+ }
/*
* Add necessary junk columns for rowmarked rels. These values are needed
@@ -123,6 +136,14 @@ preprocess_targetlist(PlannerInfo *root)
* rechecking. See comments for PlanRowMark in plannodes.h. If you
* change this stanza, see also expand_inherited_rtentry(), which has to
* be able to add on junk columns equivalent to these.
+ *
+ * (Someday it might be useful to fold these resjunk columns into the
+ * row-identity-column management used for UPDATE/DELETE. Today is not
+ * that day, however. One notable issue is that it seems important that
+ * the whole-row Vars made here use the real table rowtype, not RECORD, so
+ * that conversion to/from child relations' rowtypes will happen. Also,
+ * since these entries don't potentially bloat with more and more child
+ * relations, there's not really much need for column sharing.)
*/
foreach(lc, root->rowMarks)
{
@@ -222,6 +243,8 @@ preprocess_targetlist(PlannerInfo *root)
list_free(vars);
}
+ root->processed_tlist = tlist;
+
/*
* If there's an ON CONFLICT UPDATE clause, preprocess its targetlist too
* while we have the relation open.
@@ -235,8 +258,35 @@ preprocess_targetlist(PlannerInfo *root)
if (target_relation)
table_close(target_relation, NoLock);
+}
- return tlist;
+/*
+ * extract_update_colnos
+ * Extract a list of the target-table column numbers that
+ * an UPDATE's targetlist wants to assign to, then renumber.
+ *
+ * The convention in the parser and rewriter is that the resnos in an
+ * UPDATE's non-resjunk TLE entries are the target column numbers
+ * to assign to. Here, we extract that info into a separate list, and
+ * then convert the tlist to the sequential-numbering convention that's
+ * used by all other query types.
+ */
+static List *
+extract_update_colnos(List *tlist)
+{
+ List *update_colnos = NIL;
+ AttrNumber nextresno = 1;
+ ListCell *lc;
+
+ foreach(lc, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (!tle->resjunk)
+ update_colnos = lappend_int(update_colnos, tle->resno);
+ tle->resno = nextresno++;
+ }
+ return update_colnos;
}
@@ -251,6 +301,10 @@ preprocess_targetlist(PlannerInfo *root)
* Given a target list as generated by the parser and a result relation,
* add targetlist entries for any missing attributes, and ensure the
* non-junk attributes appear in proper field order.
+ *
+ * command_type is a bit of an archaism now: it's CMD_INSERT when we're
+ * processing an INSERT, all right, but the only other use of this function
+ * is for ON CONFLICT UPDATE tlists, for which command_type is CMD_UPDATE.
*/
static List *
expand_targetlist(List *tlist, int command_type,
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c6..af46f581ac1 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -15,9 +15,12 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/table.h"
+#include "foreign/fdwapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
+#include "optimizer/pathnode.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
@@ -37,8 +40,6 @@ static void make_inh_translation_list(Relation oldrelation,
AppendRelInfo *appinfo);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
-static List *adjust_inherited_tlist(List *tlist,
- AppendRelInfo *context);
/*
@@ -194,7 +195,6 @@ Node *
adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
AppendRelInfo **appinfos)
{
- Node *result;
adjust_appendrel_attrs_context context;
context.root = root;
@@ -204,40 +204,10 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
/* If there's nothing to adjust, don't call this function. */
Assert(nappinfos >= 1 && appinfos != NULL);
- /*
- * Must be prepared to start with a Query or a bare expression tree.
- */
- if (node && IsA(node, Query))
- {
- Query *newnode;
- int cnt;
+ /* Should never be translating a Query tree. */
+ Assert(node == NULL || !IsA(node, Query));
- newnode = query_tree_mutator((Query *) node,
- adjust_appendrel_attrs_mutator,
- (void *) &context,
- QTW_IGNORE_RC_SUBQUERIES);
- for (cnt = 0; cnt < nappinfos; cnt++)
- {
- AppendRelInfo *appinfo = appinfos[cnt];
-
- if (newnode->resultRelation == appinfo->parent_relid)
- {
- newnode->resultRelation = appinfo->child_relid;
- /* Fix tlist resnos too, if it's inherited UPDATE */
- if (newnode->commandType == CMD_UPDATE)
- newnode->targetList =
- adjust_inherited_tlist(newnode->targetList,
- appinfo);
- break;
- }
- }
-
- result = (Node *) newnode;
- }
- else
- result = adjust_appendrel_attrs_mutator(node, &context);
-
- return result;
+ return adjust_appendrel_attrs_mutator(node, &context);
}
static Node *
@@ -343,6 +313,57 @@ adjust_appendrel_attrs_mutator(Node *node,
}
/* system attributes don't need any other translation */
}
+ else if (var->varno == ROWID_VAR)
+ {
+ /*
+ * If it's a ROWID_VAR placeholder, see if we've reached a leaf
+ * target rel, for which we can translate the Var to a specific
+ * instantiation. We should never be asked to translate to a set
+ * of relids containing more than one leaf target rel, so the
+ * answer will be unique. If we're still considering non-leaf
+ * inheritance levels, return the ROWID_VAR Var as-is.
+ */
+ Relids leaf_result_relids = context->root->leaf_result_relids;
+ Index leaf_relid = 0;
+
+ for (cnt = 0; cnt < nappinfos; cnt++)
+ {
+ if (bms_is_member(appinfos[cnt]->child_relid,
+ leaf_result_relids))
+ {
+ if (leaf_relid)
+ elog(ERROR, "cannot translate to multiple leaf relids");
+ leaf_relid = appinfos[cnt]->child_relid;
+ }
+ }
+
+ if (leaf_relid)
+ {
+ RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *)
+ list_nth(context->root->row_identity_vars, var->varattno - 1);
+
+ if (bms_is_member(leaf_relid, ridinfo->rowidrels))
+ {
+ /* Substitute the Var given in the RowIdentityVarInfo */
+ var = copyObject(ridinfo->rowidvar);
+ /* ... but use the correct relid */
+ var->varno = leaf_relid;
+ /* varnosyn in the RowIdentityVarInfo is probably wrong */
+ var->varnosyn = 0;
+ var->varattnosyn = 0;
+ }
+ else
+ {
+ /*
+ * This leaf rel can't return the desired value, so
+ * substitute a NULL of the correct type.
+ */
+ return (Node *) makeNullConst(var->vartype,
+ var->vartypmod,
+ var->varcollid);
+ }
+ }
+ }
return (Node *) var;
}
if (IsA(node, CurrentOfExpr))
@@ -361,44 +382,6 @@ adjust_appendrel_attrs_mutator(Node *node,
}
return (Node *) cexpr;
}
- if (IsA(node, RangeTblRef))
- {
- RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
-
- for (cnt = 0; cnt < nappinfos; cnt++)
- {
- AppendRelInfo *appinfo = appinfos[cnt];
-
- if (rtr->rtindex == appinfo->parent_relid)
- {
- rtr->rtindex = appinfo->child_relid;
- break;
- }
- }
- return (Node *) rtr;
- }
- if (IsA(node, JoinExpr))
- {
- /* Copy the JoinExpr node with correct mutation of subnodes */
- JoinExpr *j;
- AppendRelInfo *appinfo;
-
- j = (JoinExpr *) expression_tree_mutator(node,
- adjust_appendrel_attrs_mutator,
- (void *) context);
- /* now fix JoinExpr's rtindex (probably never happens) */
- for (cnt = 0; cnt < nappinfos; cnt++)
- {
- appinfo = appinfos[cnt];
-
- if (j->rtindex == appinfo->parent_relid)
- {
- j->rtindex = appinfo->child_relid;
- break;
- }
- }
- return (Node *) j;
- }
if (IsA(node, PlaceHolderVar))
{
/* Copy the PlaceHolderVar node with correct mutation of subnodes */
@@ -486,6 +469,9 @@ adjust_appendrel_attrs_mutator(Node *node,
*/
Assert(!IsA(node, SubLink));
Assert(!IsA(node, Query));
+ /* We should never see these Query substructures, either. */
+ Assert(!IsA(node, RangeTblRef));
+ Assert(!IsA(node, JoinExpr));
return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
(void *) context);
@@ -621,100 +607,101 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
}
/*
- * Adjust the targetlist entries of an inherited UPDATE operation
- *
- * The expressions have already been fixed, but we have to make sure that
- * the target resnos match the child table (they may not, in the case of
- * a column that was added after-the-fact by ALTER TABLE). In some cases
- * this can force us to re-order the tlist to preserve resno ordering.
- * (We do all this work in special cases so that preptlist.c is fast for
- * the typical case.)
- *
- * The given tlist has already been through expression_tree_mutator;
- * therefore the TargetEntry nodes are fresh copies that it's okay to
- * scribble on.
- *
- * Note that this is not needed for INSERT because INSERT isn't inheritable.
+ * adjust_inherited_attnums
+ * Translate an integer list of attribute numbers from parent to child.
*/
-static List *
-adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
+List *
+adjust_inherited_attnums(List *attnums, AppendRelInfo *context)
{
- bool changed_it = false;
- ListCell *tl;
- List *new_tlist;
- bool more;
- int attrno;
+ List *result = NIL;
+ ListCell *lc;
/* This should only happen for an inheritance case, not UNION ALL */
Assert(OidIsValid(context->parent_reloid));
- /* Scan tlist and update resnos to match attnums of child rel */
- foreach(tl, tlist)
+ /* Look up each attribute in the AppendRelInfo's translated_vars list */
+ foreach(lc, attnums)
{
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ AttrNumber parentattno = lfirst_int(lc);
Var *childvar;
- if (tle->resjunk)
- continue; /* ignore junk items */
-
/* Look up the translation of this column: it must be a Var */
- if (tle->resno <= 0 ||
- tle->resno > list_length(context->translated_vars))
+ if (parentattno <= 0 ||
+ parentattno > list_length(context->translated_vars))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
- tle->resno, get_rel_name(context->parent_reloid));
- childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1);
+ parentattno, get_rel_name(context->parent_reloid));
+ childvar = (Var *) list_nth(context->translated_vars, parentattno - 1);
if (childvar == NULL || !IsA(childvar, Var))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
- tle->resno, get_rel_name(context->parent_reloid));
+ parentattno, get_rel_name(context->parent_reloid));
- if (tle->resno != childvar->varattno)
- {
- tle->resno = childvar->varattno;
- changed_it = true;
- }
+ result = lappend_int(result, childvar->varattno);
}
+ return result;
+}
- /*
- * If we changed anything, re-sort the tlist by resno, and make sure
- * resjunk entries have resnos above the last real resno. The sort
- * algorithm is a bit stupid, but for such a seldom-taken path, small is
- * probably better than fast.
- */
- if (!changed_it)
- return tlist;
+/*
+ * adjust_inherited_attnums_multilevel
+ * As above, but traverse multiple inheritance levels as needed.
+ */
+List *
+adjust_inherited_attnums_multilevel(PlannerInfo *root, List *attnums,
+ Index child_relid, Index top_parent_relid)
+{
+ AppendRelInfo *appinfo = root->append_rel_array[child_relid];
- new_tlist = NIL;
- more = true;
- for (attrno = 1; more; attrno++)
- {
- more = false;
- foreach(tl, tlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ if (!appinfo)
+ elog(ERROR, "child rel %d not found in append_rel_array", child_relid);
- if (tle->resjunk)
- continue; /* ignore junk items */
+ /* Recurse if immediate parent is not the top parent. */
+ if (appinfo->parent_relid != top_parent_relid)
+ attnums = adjust_inherited_attnums_multilevel(root, attnums,
+ appinfo->parent_relid,
+ top_parent_relid);
- if (tle->resno == attrno)
- new_tlist = lappend(new_tlist, tle);
- else if (tle->resno > attrno)
- more = true;
- }
- }
+ /* Now translate for this child */
+ return adjust_inherited_attnums(attnums, appinfo);
+}
- foreach(tl, tlist)
+/*
+ * get_translated_update_targetlist
+ * Get the processed_tlist of an UPDATE query, translated as needed to
+ * match a child target relation.
+ *
+ * Optionally also return the list of target column numbers translated
+ * to this target relation. (The resnos in processed_tlist MUST NOT be
+ * relied on for this purpose.)
+ */
+void
+get_translated_update_targetlist(PlannerInfo *root, Index relid,
+ List **processed_tlist, List **update_colnos)
+{
+ /* This is pretty meaningless for commands other than UPDATE. */
+ Assert(root->parse->commandType == CMD_UPDATE);
+ if (relid == root->parse->resultRelation)
{
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
-
- if (!tle->resjunk)
- continue; /* here, ignore non-junk items */
-
- tle->resno = attrno;
- new_tlist = lappend(new_tlist, tle);
- attrno++;
+ /*
+ * Non-inheritance case, so it's easy. The caller might be expecting
+ * a tree it can scribble on, though, so copy.
+ */
+ *processed_tlist = copyObject(root->processed_tlist);
+ if (update_colnos)
+ *update_colnos = copyObject(root->update_colnos);
+ }
+ else
+ {
+ Assert(bms_is_member(relid, root->all_result_relids));
+ *processed_tlist = (List *)
+ adjust_appendrel_attrs_multilevel(root,
+ (Node *) root->processed_tlist,
+ bms_make_singleton(relid),
+ bms_make_singleton(root->parse->resultRelation));
+ if (update_colnos)
+ *update_colnos =
+ adjust_inherited_attnums_multilevel(root, root->update_colnos,
+ relid,
+ root->parse->resultRelation);
}
-
- return new_tlist;
}
/*
@@ -746,3 +733,270 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+
+/*****************************************************************************
+ *
+ * ROW-IDENTITY VARIABLE MANAGEMENT
+ *
+ * This code lacks a good home, perhaps. We choose to keep it here because
+ * adjust_appendrel_attrs_mutator() is its principal co-conspirator. That
+ * function does most of what is needed to expand ROWID_VAR Vars into the
+ * right things.
+ *
+ *****************************************************************************/
+
+/*
+ * add_row_identity_var
+ * Register a row-identity column to be used in UPDATE/DELETE.
+ *
+ * The Var must be equal(), aside from varno, to any other row-identity
+ * column with the same rowid_name. Thus, for example, "wholerow"
+ * row identities had better use vartype == RECORDOID.
+ *
+ * rtindex is currently redundant with rowid_var->varno, but we specify
+ * it as a separate parameter in case this is ever generalized to support
+ * non-Var expressions. (We could reasonably handle expressions over
+ * Vars of the specified rtindex, but for now that seems unnecessary.)
+ */
+void
+add_row_identity_var(PlannerInfo *root, Var *orig_var,
+ Index rtindex, const char *rowid_name)
+{
+ TargetEntry *tle;
+ Var *rowid_var;
+ RowIdentityVarInfo *ridinfo;
+ ListCell *lc;
+
+ /* For now, the argument must be just a Var of the given rtindex */
+ Assert(IsA(orig_var, Var));
+ Assert(orig_var->varno == rtindex);
+ Assert(orig_var->varlevelsup == 0);
+
+ /*
+ * If we're doing non-inherited UPDATE/DELETE, there's little need for
+ * ROWID_VAR shenanigans. Just shove the presented Var into the
+ * processed_tlist, and we're done.
+ */
+ if (rtindex == root->parse->resultRelation)
+ {
+ tle = makeTargetEntry((Expr *) orig_var,
+ list_length(root->processed_tlist) + 1,
+ pstrdup(rowid_name),
+ true);
+ root->processed_tlist = lappend(root->processed_tlist, tle);
+ return;
+ }
+
+ /*
+ * Otherwise, rtindex should reference a leaf target relation that's being
+ * added to the query during expand_inherited_rtentry().
+ */
+ Assert(bms_is_member(rtindex, root->leaf_result_relids));
+ Assert(root->append_rel_array[rtindex] != NULL);
+
+ /*
+ * We have to find a matching RowIdentityVarInfo, or make one if there is
+ * none. To allow using equal() to match the vars, change the varno to
+ * ROWID_VAR, leaving all else alone.
+ */
+ rowid_var = copyObject(orig_var);
+ /* This could eventually become ChangeVarNodes() */
+ rowid_var->varno = ROWID_VAR;
+
+ /* Look for an existing row-id column of the same name */
+ foreach(lc, root->row_identity_vars)
+ {
+ ridinfo = (RowIdentityVarInfo *) lfirst(lc);
+ if (strcmp(rowid_name, ridinfo->rowidname) != 0)
+ continue;
+ if (equal(rowid_var, ridinfo->rowidvar))
+ {
+ /* Found a match; we need only record that rtindex needs it too */
+ ridinfo->rowidrels = bms_add_member(ridinfo->rowidrels, rtindex);
+ return;
+ }
+ else
+ {
+ /* Ooops, can't handle this */
+ elog(ERROR, "conflicting uses of row-identity name \"%s\"",
+ rowid_name);
+ }
+ }
+
+ /* No request yet, so add a new RowIdentityVarInfo */
+ ridinfo = makeNode(RowIdentityVarInfo);
+ ridinfo->rowidvar = copyObject(rowid_var);
+ /* for the moment, estimate width using just the datatype info */
+ ridinfo->rowidwidth = get_typavgwidth(exprType((Node *) rowid_var),
+ exprTypmod((Node *) rowid_var));
+ ridinfo->rowidname = pstrdup(rowid_name);
+ ridinfo->rowidrels = bms_make_singleton(rtindex);
+
+ root->row_identity_vars = lappend(root->row_identity_vars, ridinfo);
+
+ /* Change rowid_var into a reference to this row_identity_vars entry */
+ rowid_var->varattno = list_length(root->row_identity_vars);
+
+ /* Push the ROWID_VAR reference variable into processed_tlist */
+ tle = makeTargetEntry((Expr *) rowid_var,
+ list_length(root->processed_tlist) + 1,
+ pstrdup(rowid_name),
+ true);
+ root->processed_tlist = lappend(root->processed_tlist, tle);
+}
+
+/*
+ * add_row_identity_columns
+ *
+ * This function adds the row identity columns needed by the core code.
+ * FDWs might call add_row_identity_var() for themselves to add nonstandard
+ * columns. (Duplicate requests are fine.)
+ */
+void
+add_row_identity_columns(PlannerInfo *root, Index rtindex,
+ RangeTblEntry *target_rte,
+ Relation target_relation)
+{
+ CmdType commandType = root->parse->commandType;
+ char relkind = target_relation->rd_rel->relkind;
+ Var *var;
+
+ Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE);
+
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
+ relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ /*
+ * Emit CTID so that executor can find the row to update or delete.
+ */
+ var = makeVar(rtindex,
+ SelfItemPointerAttributeNumber,
+ TIDOID,
+ -1,
+ InvalidOid,
+ 0);
+ add_row_identity_var(root, var, rtindex, "ctid");
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Let the foreign table's FDW add whatever junk TLEs it wants.
+ */
+ FdwRoutine *fdwroutine;
+
+ fdwroutine = GetFdwRoutineForRelation(target_relation, false);
+
+ if (fdwroutine->AddForeignUpdateTargets != NULL)
+ fdwroutine->AddForeignUpdateTargets(root, rtindex,
+ target_rte, target_relation);
+
+ /*
+ * For UPDATE, we need to make the FDW fetch unchanged columns by
+ * asking it to fetch a whole-row Var. That's because the top-level
+ * targetlist only contains entries for changed columns, but
+ * ExecUpdate will need to build the complete new tuple. (Actually,
+ * we only really need this in UPDATEs that are not pushed to the
+ * remote side, but it's hard to tell if that will be the case at the
+ * point when this function is called.)
+ *
+ * We will also need the whole row if there are any row triggers, so
+ * that the executor will have the "old" row to pass to the trigger.
+ * Alas, this misses system columns.
+ */
+ if (commandType == CMD_UPDATE ||
+ (target_relation->trigdesc &&
+ (target_relation->trigdesc->trig_delete_after_row ||
+ target_relation->trigdesc->trig_delete_before_row)))
+ {
+ var = makeVar(rtindex,
+ InvalidAttrNumber,
+ RECORDOID,
+ -1,
+ InvalidOid,
+ 0);
+ add_row_identity_var(root, var, rtindex, "wholerow");
+ }
+ }
+}
+
+/*
+ * distribute_row_identity_vars
+ *
+ * After we have finished identifying all the row identity columns
+ * needed by an inherited UPDATE/DELETE query, make sure that these
+ * columns will be generated by all the target relations.
+ *
+ * This is more or less like what build_base_rel_tlists() does,
+ * except that it would not understand what to do with ROWID_VAR Vars.
+ * Since that function runs before inheritance relations are expanded,
+ * it will never see any such Vars anyway.
+ */
+void
+distribute_row_identity_vars(PlannerInfo *root)
+{
+ Query *parse = root->parse;
+ int result_relation = parse->resultRelation;
+ RangeTblEntry *target_rte;
+ RelOptInfo *target_rel;
+ ListCell *lc;
+
+ /* There's nothing to do if this isn't an inherited UPDATE/DELETE. */
+ if (parse->commandType != CMD_UPDATE && parse->commandType != CMD_DELETE)
+ {
+ Assert(root->row_identity_vars == NIL);
+ return;
+ }
+ target_rte = rt_fetch(result_relation, parse->rtable);
+ if (!target_rte->inh)
+ {
+ Assert(root->row_identity_vars == NIL);
+ return;
+ }
+
+ /*
+ * Ordinarily, we expect that leaf result relation(s) will have added some
+ * ROWID_VAR Vars to the query. However, it's possible that constraint
+ * exclusion suppressed every leaf relation. The executor will get upset
+ * if the plan has no row identity columns at all, even though it will
+ * certainly process no rows. Handle this edge case by re-opening the top
+ * result relation and adding the row identity columns it would have used,
+ * as preprocess_targetlist() would have done if it weren't marked "inh".
+ * (This is a bit ugly, but it seems better to confine the ugliness and
+ * extra cycles to this unusual corner case.) We needn't worry about
+ * fixing the rel's reltarget, as that won't affect the finished plan.
+ */
+ if (root->row_identity_vars == NIL)
+ {
+ Relation target_relation;
+
+ target_relation = table_open(target_rte->relid, NoLock);
+ add_row_identity_columns(root, result_relation,
+ target_rte, target_relation);
+ table_close(target_relation, NoLock);
+ return;
+ }
+
+ /*
+ * Dig through the processed_tlist to find the ROWID_VAR reference Vars,
+ * and forcibly copy them into the reltarget list of the topmost target
+ * relation. That's sufficient because they'll be copied to the
+ * individual leaf target rels (with appropriate translation) later,
+ * during appendrel expansion --- see set_append_rel_size().
+ */
+ target_rel = find_base_rel(root, result_relation);
+
+ foreach(lc, root->processed_tlist)
+ {
+ TargetEntry *tle = lfirst(lc);
+ Var *var = (Var *) tle->expr;
+
+ if (var && IsA(var, Var) && var->varno == ROWID_VAR)
+ {
+ target_rel->reltarget->exprs =
+ lappend(target_rel->reltarget->exprs, copyObject(var));
+ /* reltarget cost and width will be computed later */
+ }
+ }
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd964..13f67ab7449 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -219,6 +219,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
* targetlist and update parent rel's reltarget. This should match what
* preprocess_targetlist() would have added if the mark types had been
* requested originally.
+ *
+ * (Someday it might be useful to fold these resjunk columns into the
+ * row-identity-column management used for UPDATE/DELETE. Today is not
+ * that day, however.)
*/
if (oldrc)
{
@@ -585,6 +589,46 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
root->rowMarks = lappend(root->rowMarks, childrc);
}
+
+ /*
+ * If we are creating a child of the query target relation (only possible
+ * in UPDATE/DELETE), add it to all_result_relids, as well as
+ * leaf_result_relids if appropriate, and make sure that we generate
+ * required row-identity data.
+ */
+ if (bms_is_member(parentRTindex, root->all_result_relids))
+ {
+ /* OK, record the child as a result rel too. */
+ root->all_result_relids = bms_add_member(root->all_result_relids,
+ childRTindex);
+
+ /* Non-leaf partitions don't need any row identity info. */
+ if (childrte->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ Var *rrvar;
+
+ root->leaf_result_relids = bms_add_member(root->leaf_result_relids,
+ childRTindex);
+
+ /*
+ * If we have any child target relations, assume they all need to
+ * generate a junk "tableoid" column. (If only one child survives
+ * pruning, we wouldn't really need this, but it's not worth
+ * thrashing about to avoid it.)
+ */
+ rrvar = makeVar(childRTindex,
+ TableOidAttributeNumber,
+ OIDOID,
+ -1,
+ InvalidOid,
+ 0);
+ add_row_identity_var(root, rrvar, childRTindex, "tableoid");
+
+ /* Register any row-identity columns needed by this child. */
+ add_row_identity_columns(root, childRTindex,
+ childrte, childrel);
+ }
+ }
}
/*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d5c66780ac8..1c47a2fb49c 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3540,6 +3540,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* Creates a pathnode that represents performing INSERT/UPDATE/DELETE mods
*
* 'rel' is the parent relation associated with the result
+ * 'subpath' is a Path producing source data
* 'operation' is the operation type
* 'canSetTag' is true if we set the command tag/es_processed
* 'nominalRelation' is the parent RT index for use of EXPLAIN
@@ -3547,8 +3548,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* 'partColsUpdated' is true if any partitioning columns are being updated,
* either from the target relation or a descendent partitioned table.
* 'resultRelations' is an integer list of actual RT indexes of target rel(s)
- * 'subpaths' is a list of Path(s) producing source data (one per rel)
- * 'subroots' is a list of PlannerInfo structs (one per rel)
+ * 'updateColnosLists' is a list of UPDATE target column number lists
+ * (one sublist per rel); or NIL if not an UPDATE
* 'withCheckOptionLists' is a list of WCO lists (one per rel)
* 'returningLists' is a list of RETURNING tlists (one per rel)
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3557,21 +3558,21 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
*/
ModifyTablePath *
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
+ Path *subpath,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
- List *resultRelations, List *subpaths,
- List *subroots,
+ List *resultRelations,
+ List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam)
{
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
- double total_size;
- ListCell *lc;
- Assert(list_length(resultRelations) == list_length(subpaths));
- Assert(list_length(resultRelations) == list_length(subroots));
+ Assert(operation == CMD_UPDATE ?
+ list_length(resultRelations) == list_length(updateColnosLists) :
+ updateColnosLists == NIL);
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
@@ -3589,7 +3590,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.pathkeys = NIL;
/*
- * Compute cost & rowcount as sum of subpath costs & rowcounts.
+ * Compute cost & rowcount as subpath cost & rowcount (if RETURNING)
*
* Currently, we don't charge anything extra for the actual table
* modification work, nor for the WITH CHECK OPTIONS or RETURNING
@@ -3598,42 +3599,34 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
* costs to change any higher-level planning choices. But we might want
* to make it look better sometime.
*/
- pathnode->path.startup_cost = 0;
- pathnode->path.total_cost = 0;
- pathnode->path.rows = 0;
- total_size = 0;
- foreach(lc, subpaths)
+ pathnode->path.startup_cost = subpath->startup_cost;
+ pathnode->path.total_cost = subpath->total_cost;
+ if (returningLists != NIL)
{
- Path *subpath = (Path *) lfirst(lc);
+ pathnode->path.rows = subpath->rows;
- if (lc == list_head(subpaths)) /* first node? */
- pathnode->path.startup_cost = subpath->startup_cost;
- pathnode->path.total_cost += subpath->total_cost;
- if (returningLists != NIL)
- {
- pathnode->path.rows += subpath->rows;
- total_size += subpath->pathtarget->width * subpath->rows;
- }
+ /*
+ * Set width to match the subpath output. XXX this is totally wrong:
+ * we should return an average of the RETURNING tlist widths. But
+ * it's what happened historically, and improving it is a task for
+ * another day. (Again, it's mostly window dressing.)
+ */
+ pathnode->path.pathtarget->width = subpath->pathtarget->width;
+ }
+ else
+ {
+ pathnode->path.rows = 0;
+ pathnode->path.pathtarget->width = 0;
}
- /*
- * Set width to the average width of the subpath outputs. XXX this is
- * totally wrong: we should return an average of the RETURNING tlist
- * widths. But it's what happened historically, and improving it is a task
- * for another day.
- */
- if (pathnode->path.rows > 0)
- total_size /= pathnode->path.rows;
- pathnode->path.pathtarget->width = rint(total_size);
-
+ pathnode->subpath = subpath;
pathnode->operation = operation;
pathnode->canSetTag = canSetTag;
pathnode->nominalRelation = nominalRelation;
pathnode->rootRelation = rootRelation;
pathnode->partColsUpdated = partColsUpdated;
pathnode->resultRelations = resultRelations;
- pathnode->subpaths = subpaths;
- pathnode->subroots = subroots;
+ pathnode->updateColnosLists = updateColnosLists;
pathnode->withCheckOptionLists = withCheckOptionLists;
pathnode->returningLists = returningLists;
pathnode->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 0fa8875f091..345c7425f60 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1515,18 +1515,11 @@ relation_excluded_by_constraints(PlannerInfo *root,
/*
* When constraint_exclusion is set to 'partition' we only handle
- * appendrel members. Normally, they are RELOPT_OTHER_MEMBER_REL
- * relations, but we also consider inherited target relations as
- * appendrel members for the purposes of constraint exclusion
- * (since, indeed, they were appendrel members earlier in
- * inheritance_planner).
- *
- * In both cases, partition pruning was already applied, so there
- * is no need to consider the rel's partition constraints here.
+ * appendrel members. Partition pruning has already been applied,
+ * so there is no need to consider the rel's partition constraints
+ * here.
*/
- if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
- (rel->relid == root->parse->resultRelation &&
- root->inhTargetKind != INHKIND_NONE))
+ if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
break; /* appendrel member, so process it */
return false;
@@ -1539,9 +1532,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
* its partition constraints haven't been considered yet, so
* include them in the processing here.
*/
- if (rel->reloptkind == RELOPT_BASEREL &&
- !(rel->relid == root->parse->resultRelation &&
- root->inhTargetKind != INHKIND_NONE))
+ if (rel->reloptkind == RELOPT_BASEREL)
include_partition = true;
break; /* always try to exclude */
}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 345c877aeb3..e105a4d5f1d 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -977,8 +977,6 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
foreach(vars, input_rel->reltarget->exprs)
{
Var *var = (Var *) lfirst(vars);
- RelOptInfo *baserel;
- int ndx;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
@@ -996,17 +994,35 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
elog(ERROR, "unexpected node type in rel targetlist: %d",
(int) nodeTag(var));
- /* Get the Var's original base rel */
- baserel = find_base_rel(root, var->varno);
-
- /* Is it still needed above this joinrel? */
- ndx = var->varattno - baserel->min_attr;
- if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
+ if (var->varno == ROWID_VAR)
{
- /* Yup, add it to the output */
- joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
+ /* UPDATE/DELETE row identity vars are always needed */
+ RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *)
+ list_nth(root->row_identity_vars, var->varattno - 1);
+
+ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
+ var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
- joinrel->reltarget->width += baserel->attr_widths[ndx];
+ joinrel->reltarget->width += ridinfo->rowidwidth;
+ }
+ else
+ {
+ RelOptInfo *baserel;
+ int ndx;
+
+ /* Get the Var's original base rel */
+ baserel = find_base_rel(root, var->varno);
+
+ /* Is it still needed above this joinrel? */
+ ndx = var->varattno - baserel->min_attr;
+ if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
+ {
+ /* Yup, add it to the output */
+ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
+ var);
+ /* Vars have cost zero, so no need to adjust reltarget->cost */
+ joinrel->reltarget->width += baserel->attr_widths[ndx];
+ }
}
}
}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 0672f497c6b..92661abae23 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -705,16 +705,9 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
*
* We must do items 1,2,3 before firing rewrite rules, else rewritten
* references to NEW.foo will produce wrong or incomplete results. Item 4
- * is not needed for rewriting, but will be needed by the planner, and we
+ * is not needed for rewriting, but it is helpful for the planner, and we
* can do it essentially for free while handling the other items.
*
- * Note that for an inheritable UPDATE, this processing is only done once,
- * using the parent relation as reference. It must not do anything that
- * will not be correct when transposed to the child relation(s). (Step 4
- * is incorrect by this light, since child relations might have different
- * column ordering, but the planner will fix things by re-sorting the tlist
- * for each child.)
- *
* If values_rte is non-NULL (i.e., we are doing a multi-row INSERT using
* values from a VALUES RTE), we populate *unused_values_attrnos with the
* attribute numbers of any unused columns from the VALUES RTE. This can
@@ -1608,90 +1601,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
/*
- * rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed
- *
- * This function adds a "junk" TLE that is needed to allow the executor to
- * find the original row for the update or delete. When the target relation
- * is a regular table, the junk TLE emits the ctid attribute of the original
- * row. When the target relation is a foreign table, we let the FDW decide
- * what to add.
- *
- * We used to do this during RewriteQuery(), but now that inheritance trees
- * can contain a mix of regular and foreign tables, we must postpone it till
- * planning, after the inheritance tree has been expanded. In that way we
- * can do the right thing for each child table.
- */
-void
-rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
- Relation target_relation)
-{
- Var *var = NULL;
- const char *attrname;
- TargetEntry *tle;
-
- if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
- target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
- target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
- {
- /*
- * Emit CTID so that executor can find the row to update or delete.
- */
- var = makeVar(parsetree->resultRelation,
- SelfItemPointerAttributeNumber,
- TIDOID,
- -1,
- InvalidOid,
- 0);
-
- attrname = "ctid";
- }
- else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
- {
- /*
- * Let the foreign table's FDW add whatever junk TLEs it wants.
- */
- FdwRoutine *fdwroutine;
-
- fdwroutine = GetFdwRoutineForRelation(target_relation, false);
-
- if (fdwroutine->AddForeignUpdateTargets != NULL)
- fdwroutine->AddForeignUpdateTargets(parsetree, target_rte,
- target_relation);
-
- /*
- * If we have a row-level trigger corresponding to the operation, emit
- * a whole-row Var so that executor will have the "old" row to pass to
- * the trigger. Alas, this misses system columns.
- */
- if (target_relation->trigdesc &&
- ((parsetree->commandType == CMD_UPDATE &&
- (target_relation->trigdesc->trig_update_after_row ||
- target_relation->trigdesc->trig_update_before_row)) ||
- (parsetree->commandType == CMD_DELETE &&
- (target_relation->trigdesc->trig_delete_after_row ||
- target_relation->trigdesc->trig_delete_before_row))))
- {
- var = makeWholeRowVar(target_rte,
- parsetree->resultRelation,
- 0,
- false);
-
- attrname = "wholerow";
- }
- }
-
- if (var != NULL)
- {
- tle = makeTargetEntry((Expr *) var,
- list_length(parsetree->targetList) + 1,
- pstrdup(attrname),
- true);
-
- parsetree->targetList = lappend(parsetree->targetList, tle);
- }
-}
-
-/*
* Record in target_rte->extraUpdatedCols the indexes of any generated columns
* that depend on any columns mentioned in target_rte->updatedCols.
*/
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 82b73726f13..254e8f30501 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -4741,16 +4741,12 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan)
* We special-case Append and MergeAppend to pretend that the first child
* plan is the OUTER referent; we have to interpret OUTER Vars in their
* tlists according to one of the children, and the first one is the most
- * natural choice. Likewise special-case ModifyTable to pretend that the
- * first child plan is the OUTER referent; this is to support RETURNING
- * lists containing references to non-target relations.
+ * natural choice.
*/
if (IsA(plan, Append))
dpns->outer_plan = linitial(((Append *) plan)->appendplans);
else if (IsA(plan, MergeAppend))
dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
- else if (IsA(plan, ModifyTable))
- dpns->outer_plan = linitial(((ModifyTable *) plan)->plans);
else
dpns->outer_plan = outerPlan(plan);