summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
-rw-r--r--src/include/executor/executor.h31
-rw-r--r--src/include/foreign/fdwapi.h3
-rw-r--r--src/include/nodes/execnodes.h55
-rw-r--r--src/include/nodes/nodes.h1
-rw-r--r--src/include/nodes/pathnodes.h84
-rw-r--r--src/include/nodes/plannodes.h8
-rw-r--r--src/include/nodes/primnodes.h20
-rw-r--r--src/include/optimizer/appendinfo.h17
-rw-r--r--src/include/optimizer/pathnode.h5
-rw-r--r--src/include/optimizer/prep.h2
-rw-r--r--src/include/rewrite/rewriteHandler.h2
-rw-r--r--src/test/regress/expected/inherit.out22
-rw-r--r--src/test/regress/expected/insert_conflict.out2
-rw-r--r--src/test/regress/expected/partition_join.out42
-rw-r--r--src/test/regress/expected/partition_prune.out199
-rw-r--r--src/test/regress/expected/rowsecurity.out135
-rw-r--r--src/test/regress/expected/updatable_views.out149
-rw-r--r--src/test/regress/expected/update.out43
-rw-r--r--src/test/regress/expected/with.out56
48 files changed, 2000 insertions, 1864 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);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 071e363d540..34dd861eff0 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -156,9 +156,6 @@ extern void ResetTupleHashTable(TupleHashTable hashtable);
*/
extern JunkFilter *ExecInitJunkFilter(List *targetList,
TupleTableSlot *slot);
-extern JunkFilter *ExecInitJunkFilterInsertion(List *targetList,
- TupleDesc cleanTupType,
- TupleTableSlot *slot);
extern JunkFilter *ExecInitJunkFilterConversion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot);
@@ -166,11 +163,24 @@ extern AttrNumber ExecFindJunkAttribute(JunkFilter *junkfilter,
const char *attrName);
extern AttrNumber ExecFindJunkAttributeInTlist(List *targetlist,
const char *attrName);
-extern Datum ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno,
- bool *isNull);
extern TupleTableSlot *ExecFilterJunk(JunkFilter *junkfilter,
TupleTableSlot *slot);
+/*
+ * 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.
+ */
+#ifndef FRONTEND
+static inline Datum
+ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull)
+{
+ Assert(attno > 0);
+ return slot_getattr(slot, attno, isNull);
+}
+#endif
/*
* prototypes from functions in execMain.c
@@ -270,6 +280,12 @@ extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
TupleTableSlot *slot,
PlanState *parent,
TupleDesc inputDesc);
+extern ProjectionInfo *ExecBuildUpdateProjection(List *subTargetList,
+ List *targetColnos,
+ TupleDesc relDesc,
+ ExprContext *econtext,
+ TupleTableSlot *slot,
+ PlanState *parent);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern ExprState *ExecPrepareQual(List *qual, EState *estate);
extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
@@ -622,4 +638,9 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
const char *relname);
+/* needed by trigger.c */
+extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+ TupleTableSlot *planSlot,
+ TupleTableSlot *oldSlot);
+
#endif /* EXECUTOR_H */
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 7c89d081c76..10d29ff292a 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -65,7 +65,8 @@ typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
RelOptInfo *output_rel,
void *extra);
-typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
+typedef void (*AddForeignUpdateTargets_function) (PlannerInfo *root,
+ Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 09ea7ef6a6b..3b39369a492 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -356,10 +356,6 @@ typedef struct ProjectionInfo
* attribute numbers of the "original" tuple and the
* attribute numbers of the "clean" tuple.
* resultSlot: tuple slot used to hold cleaned tuple.
- * junkAttNo: not used by junkfilter code. Can be used by caller
- * to remember the attno of a specific junk attribute
- * (nodeModifyTable.c keeps the "ctid" or "wholerow"
- * attno here).
* ----------------
*/
typedef struct JunkFilter
@@ -369,7 +365,6 @@ typedef struct JunkFilter
TupleDesc jf_cleanTupType;
AttrNumber *jf_cleanMap;
TupleTableSlot *jf_resultSlot;
- AttrNumber jf_junkAttNo;
} JunkFilter;
/*
@@ -423,6 +418,19 @@ typedef struct ResultRelInfo
/* array of key/attr info for indices */
IndexInfo **ri_IndexRelationInfo;
+ /*
+ * For UPDATE/DELETE result relations, the attribute number of the row
+ * identity junk attribute in the source plan's output tuples
+ */
+ AttrNumber ri_RowIdAttNo;
+
+ /* Projection to generate new tuple in an INSERT/UPDATE */
+ ProjectionInfo *ri_projectNew;
+ /* Slot to hold that tuple */
+ TupleTableSlot *ri_newTupleSlot;
+ /* Slot to hold the old tuple being updated */
+ TupleTableSlot *ri_oldTupleSlot;
+
/* triggers to be fired, if any */
TriggerDesc *ri_TrigDesc;
@@ -470,9 +478,6 @@ typedef struct ResultRelInfo
/* number of stored generated columns we need to compute */
int ri_NumGeneratedNeeded;
- /* for removing junk attributes from tuples */
- JunkFilter *ri_junkFilter;
-
/* list of RETURNING expressions */
List *ri_returningList;
@@ -677,10 +682,7 @@ typedef struct ExecRowMark
* Each LockRows and ModifyTable node keeps a list of the rowmarks it needs to
* deal with. In addition to a pointer to the related entry in es_rowmarks,
* this struct carries the column number(s) of the resjunk columns associated
- * with the rowmark (see comments for PlanRowMark for more detail). In the
- * case of ModifyTable, there has to be a separate ExecAuxRowMark list for
- * each child plan, because the resjunk columns could be at different physical
- * column positions in different subplans.
+ * with the rowmark (see comments for PlanRowMark for more detail).
*/
typedef struct ExecAuxRowMark
{
@@ -1082,9 +1084,8 @@ typedef struct PlanState
* EvalPlanQualSlot), and/or found using the rowmark mechanism (non-locking
* rowmarks by the EPQ machinery itself, locking ones by the caller).
*
- * While the plan to be checked may be changed using EvalPlanQualSetPlan() -
- * e.g. so all source plans for a ModifyTable node can be processed - all such
- * plans need to share the same EState.
+ * While the plan to be checked may be changed using EvalPlanQualSetPlan(),
+ * all such plans need to share the same EState.
*/
typedef struct EPQState
{
@@ -1178,24 +1179,32 @@ typedef struct ModifyTableState
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
- PlanState **mt_plans; /* subplans (one per target rel) */
- int mt_nplans; /* number of plans in the array */
- int mt_whichplan; /* which one is being executed (0..n-1) */
- TupleTableSlot **mt_scans; /* input tuple corresponding to underlying
- * plans */
- ResultRelInfo *resultRelInfo; /* per-subplan target relations */
+ int mt_nrels; /* number of entries in resultRelInfo[] */
+ ResultRelInfo *resultRelInfo; /* info about target relation(s) */
/*
* Target relation mentioned in the original statement, used to fire
- * statement-level triggers and as the root for tuple routing.
+ * statement-level triggers and as the root for tuple routing. (This
+ * might point to one of the resultRelInfo[] entries, but it can also be a
+ * distinct struct.)
*/
ResultRelInfo *rootResultRelInfo;
- List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
/*
+ * These fields are used for inherited UPDATE and DELETE, to track which
+ * target relation a given tuple is from. If there are a lot of target
+ * relations, we use a hash table to translate table OIDs to
+ * resultRelInfo[] indexes; otherwise mt_resultOidHash is NULL.
+ */
+ int mt_resultOidAttno; /* resno of "tableoid" junk attr */
+ Oid mt_lastResultOid; /* last-seen value of tableoid */
+ int mt_lastResultIndex; /* corresponding index in resultRelInfo[] */
+ HTAB *mt_resultOidHash; /* optional hash table to speed lookups */
+
+ /*
* Slot for storing tuples in the root partitioned table's rowtype during
* an UPDATE of a partitioned table.
*/
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 299956f3298..704f00fd300 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -270,6 +270,7 @@ typedef enum NodeTag
T_PlaceHolderVar,
T_SpecialJoinInfo,
T_AppendRelInfo,
+ T_RowIdentityVarInfo,
T_PlaceHolderInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index d2d3643bea9..e4e1c15986f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -77,18 +77,6 @@ typedef enum UpperRelationKind
/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
} UpperRelationKind;
-/*
- * This enum identifies which type of relation is being planned through the
- * inheritance planner. INHKIND_NONE indicates the inheritance planner
- * was not used.
- */
-typedef enum InheritanceKind
-{
- INHKIND_NONE,
- INHKIND_INHERITED,
- INHKIND_PARTITIONED
-} InheritanceKind;
-
/*----------
* PlannerGlobal
* Global information for planning/optimization
@@ -277,12 +265,25 @@ struct PlannerInfo
List *join_info_list; /* list of SpecialJoinInfos */
/*
+ * all_result_relids is empty for SELECT, otherwise it contains at least
+ * parse->resultRelation. For UPDATE/DELETE across an inheritance or
+ * partitioning tree, the result rel's child relids are added. When using
+ * multi-level partitioning, intermediate partitioned rels are included.
+ * leaf_result_relids is similar except that only actual result tables,
+ * not partitioned tables, are included in it.
+ */
+ Relids all_result_relids; /* set of all result relids */
+ Relids leaf_result_relids; /* set of all leaf relids */
+
+ /*
* Note: for AppendRelInfos describing partitions of a partitioned table,
* we guarantee that partitions that come earlier in the partitioned
* table's PartitionDesc will appear earlier in append_rel_list.
*/
List *append_rel_list; /* list of AppendRelInfos */
+ List *row_identity_vars; /* list of RowIdentityVarInfos */
+
List *rowMarks; /* list of PlanRowMarks */
List *placeholder_list; /* list of PlaceHolderInfos */
@@ -309,15 +310,23 @@ struct PlannerInfo
/*
* The fully-processed targetlist is kept here. It differs from
- * parse->targetList in that (for INSERT and UPDATE) it's been reordered
- * to match the target table, and defaults have been filled in. Also,
- * additional resjunk targets may be present. preprocess_targetlist()
- * does most of this work, but note that more resjunk targets can get
- * added during appendrel expansion. (Hence, upper_targets mustn't get
- * set up till after that.)
+ * parse->targetList in that (for INSERT) it's been reordered to match the
+ * target table, and defaults have been filled in. Also, additional
+ * resjunk targets may be present. preprocess_targetlist() does most of
+ * that work, but note that more resjunk targets can get added during
+ * appendrel expansion. (Hence, upper_targets mustn't get set up till
+ * after that.)
*/
List *processed_tlist;
+ /*
+ * For UPDATE, this list contains the target table's attribute numbers to
+ * which the first N entries of processed_tlist are to be assigned. (Any
+ * additional entries in processed_tlist must be resjunk.) DO NOT use the
+ * resnos in processed_tlist to identify the UPDATE target columns.
+ */
+ List *update_colnos;
+
/* Fields filled during create_plan() for use in setrefs.c */
AttrNumber *grouping_map; /* for GroupingFunc fixup */
List *minmax_aggs; /* List of MinMaxAggInfos */
@@ -333,9 +342,6 @@ struct PlannerInfo
Index qual_security_level; /* minimum security_level for quals */
/* Note: qual_security_level is zero if there are no securityQuals */
- InheritanceKind inhTargetKind; /* indicates if the target relation is an
- * inheritance child or partition or a
- * partitioned table */
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */
bool hasHavingQual; /* true if havingQual was non-null */
@@ -1839,20 +1845,20 @@ typedef struct LockRowsPath
* ModifyTablePath represents performing INSERT/UPDATE/DELETE modifications
*
* We represent most things that will be in the ModifyTable plan node
- * literally, except we have child Path(s) not Plan(s). But analysis of the
+ * literally, except we have a child Path not Plan. But analysis of the
* OnConflictExpr is deferred to createplan.c, as is collection of FDW data.
*/
typedef struct ModifyTablePath
{
Path path;
+ Path *subpath; /* Path producing source data */
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
Index rootRelation; /* Root RT index, if target is partitioned */
- bool partColsUpdated; /* some part key in hierarchy updated */
+ bool partColsUpdated; /* some part key in hierarchy updated? */
List *resultRelations; /* integer list of RT indexes */
- List *subpaths; /* Path(s) producing source data */
- List *subroots; /* per-target-table PlannerInfos */
+ List *updateColnosLists; /* per-target-table update_colnos lists */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
@@ -2312,6 +2318,34 @@ typedef struct AppendRelInfo
} AppendRelInfo;
/*
+ * Information about a row-identity "resjunk" column in UPDATE/DELETE.
+ *
+ * In partitioned UPDATE/DELETE it's important for child partitions to share
+ * row-identity columns whenever possible, so as not to chew up too many
+ * targetlist columns. We use these structs to track which identity columns
+ * have been requested. In the finished plan, each of these will give rise
+ * to one resjunk entry in the targetlist of the ModifyTable's subplan node.
+ *
+ * All the Vars stored in RowIdentityVarInfos must have varno ROWID_VAR, for
+ * convenience of detecting duplicate requests. We'll replace that, in the
+ * final plan, with the varno of the generating rel.
+ *
+ * Outside this list, a Var with varno ROWID_VAR and varattno k is a reference
+ * to the k-th element of the row_identity_vars list (k counting from 1).
+ * We add such a reference to root->processed_tlist when creating the entry,
+ * and it propagates into the plan tree from there.
+ */
+typedef struct RowIdentityVarInfo
+{
+ NodeTag type;
+
+ Var *rowidvar; /* Var to be evaluated (but varno=ROWID_VAR) */
+ int32 rowidwidth; /* estimated average width */
+ char *rowidname; /* name of the resjunk column */
+ Relids rowidrels; /* RTE indexes of target rels using this */
+} RowIdentityVarInfo;
+
+/*
* For each distinct placeholder expression generated during planning, we
* store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list.
* This stores info that is needed centrally rather than in each copy of the
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 24ca616740b..623dc450ee4 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -201,7 +201,7 @@ typedef struct ProjectSet
/* ----------------
* ModifyTable node -
- * Apply rows produced by subplan(s) to result table(s),
+ * Apply rows produced by outer plan to result table(s),
* by inserting, updating, or deleting.
*
* If the originally named target table is a partitioned table, both
@@ -211,7 +211,7 @@ typedef struct ProjectSet
* EXPLAIN should claim is the INSERT/UPDATE/DELETE target.
*
* Note that rowMarks and epqParam are presumed to be valid for all the
- * subplan(s); they can't contain any info that varies across subplans.
+ * table(s); they can't contain any info that varies across tables.
* ----------------
*/
typedef struct ModifyTable
@@ -221,9 +221,9 @@ typedef struct ModifyTable
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
Index rootRelation; /* Root RT index, if target is partitioned */
- bool partColsUpdated; /* some part key in hierarchy updated */
+ bool partColsUpdated; /* some part key in hierarchy updated? */
List *resultRelations; /* integer list of RT indexes */
- List *plans; /* plan(s) producing source data */
+ List *updateColnosLists; /* per-target-table update_colnos lists */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f66e1449d86..f2ac4e51f16 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -158,6 +158,10 @@ typedef struct Expr
* than a heap column. (In ForeignScan and CustomScan plan nodes, INDEX_VAR
* is abused to signify references to columns of a custom scan tuple type.)
*
+ * ROWID_VAR is used in the planner to identify nonce variables that carry
+ * row identity information during UPDATE/DELETE. This value should never
+ * be seen outside the planner.
+ *
* In the parser, varnosyn and varattnosyn are either identical to
* varno/varattno, or they specify the column's position in an aliased JOIN
* RTE that hides the semantic referent RTE's refname. This is a syntactic
@@ -171,6 +175,7 @@ typedef struct Expr
#define INNER_VAR 65000 /* reference to inner subplan */
#define OUTER_VAR 65001 /* reference to outer subplan */
#define INDEX_VAR 65002 /* reference to index column */
+#define ROWID_VAR 65003 /* row identity column during planning */
#define IS_SPECIAL_VARNO(varno) ((varno) >= INNER_VAR)
@@ -1386,13 +1391,14 @@ typedef struct InferenceElem
* column for the item; so there may be missing or out-of-order resnos.
* It is even legal to have duplicated resnos; consider
* UPDATE table SET arraycol[1] = ..., arraycol[2] = ..., ...
- * The two meanings come together in the executor, because the planner
- * transforms INSERT/UPDATE tlists into a normalized form with exactly
- * one entry for each column of the destination table. Before that's
- * happened, however, it is risky to assume that resno == position.
- * Generally get_tle_by_resno() should be used rather than list_nth()
- * to fetch tlist entries by resno, and only in SELECT should you assume
- * that resno is a unique identifier.
+ * In an INSERT, the rewriter and planner will normalize the tlist by
+ * reordering it into physical column order and filling in default values
+ * for any columns not assigned values by the original query. In an UPDATE,
+ * after the rewriter merges multiple assignments for the same column, the
+ * planner extracts the target-column numbers into a separate "update_colnos"
+ * list, and then renumbers the tlist elements serially. Thus, tlist resnos
+ * match ordinal position in all tlists seen by the executor; but it is wrong
+ * to assume that before planning has happened.
*
* resname is required to represent the correct column name in non-resjunk
* entries of top-level SELECT targetlists, since it will be used as the
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc2..39d04d9cc02 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -28,8 +28,23 @@ extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
extern Relids adjust_child_relids(Relids relids, int nappinfos,
AppendRelInfo **appinfos);
extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
- Relids child_relids, Relids top_parent_relids);
+ Relids child_relids,
+ Relids top_parent_relids);
+extern List *adjust_inherited_attnums(List *attnums, AppendRelInfo *context);
+extern List *adjust_inherited_attnums_multilevel(PlannerInfo *root,
+ List *attnums,
+ Index child_relid,
+ Index top_parent_relid);
+extern void get_translated_update_targetlist(PlannerInfo *root, Index relid,
+ List **processed_tlist,
+ List **update_colnos);
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern void add_row_identity_var(PlannerInfo *root, Var *rowid_var,
+ Index rtindex, const char *rowid_name);
+extern void add_row_identity_columns(PlannerInfo *root, Index rtindex,
+ RangeTblEntry *target_rte,
+ Relation target_relation);
+extern void distribute_row_identity_vars(PlannerInfo *root);
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 54f4b782fc7..d539bc27830 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -260,11 +260,12 @@ extern LockRowsPath *create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, List *rowMarks, int epqParam);
extern 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);
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index f49196a4d38..b1c4065689c 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -34,7 +34,7 @@ extern Relids get_relids_for_join(Query *query, int joinrelid);
/*
* prototypes for preptlist.c
*/
-extern List *preprocess_targetlist(PlannerInfo *root);
+extern void preprocess_targetlist(PlannerInfo *root);
extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 1fea1a4691a..728a60c0b06 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -23,8 +23,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
bool forUpdatePushedDown);
extern Node *build_column_default(Relation rel, int attrno);
-extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
- Relation target_relation);
extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
Relation target_relation);
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef6548..1c703c351fe 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -545,27 +545,25 @@ create table some_tab_child () inherits (some_tab);
insert into some_tab_child values(1,2);
explain (verbose, costs off)
update some_tab set a = a + 1 where false;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+--------------------------------------------------------
Update on public.some_tab
- Update on public.some_tab
-> Result
- Output: (a + 1), b, ctid
+ Output: (some_tab.a + 1), NULL::oid, NULL::tid
One-Time Filter: false
-(5 rows)
+(4 rows)
update some_tab set a = a + 1 where false;
explain (verbose, costs off)
update some_tab set a = a + 1 where false returning b, a;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+--------------------------------------------------------
Update on public.some_tab
- Output: b, a
- Update on public.some_tab
+ Output: some_tab.b, some_tab.a
-> Result
- Output: (a + 1), b, ctid
+ Output: (some_tab.a + 1), NULL::oid, NULL::tid
One-Time Filter: false
-(6 rows)
+(5 rows)
update some_tab set a = a + 1 where false returning b, a;
b | a
@@ -670,7 +668,7 @@ explain update parted_tab set a = 2 where false;
QUERY PLAN
--------------------------------------------------------
Update on parted_tab (cost=0.00..0.00 rows=0 width=0)
- -> Result (cost=0.00..0.00 rows=0 width=0)
+ -> Result (cost=0.00..0.00 rows=0 width=10)
One-Time Filter: false
(3 rows)
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 499245068a5..a54a51e5c72 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -213,7 +213,7 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb
"Plans": [ +
{ +
"Node Type": "Result", +
- "Parent Relationship": "Member", +
+ "Parent Relationship": "Outer", +
"Parallel Aware": false, +
"Async Capable": false +
} +
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 0057f41caaf..27f7525b3e9 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -1926,37 +1926,27 @@ WHERE EXISTS (
FROM int4_tbl,
LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss
WHERE prt1_l.c IS NULL);
- QUERY PLAN
----------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------
Delete on prt1_l
Delete on prt1_l_p1 prt1_l_1
Delete on prt1_l_p3_p1 prt1_l_2
Delete on prt1_l_p3_p2 prt1_l_3
-> Nested Loop Semi Join
- -> Seq Scan on prt1_l_p1 prt1_l_1
- Filter: (c IS NULL)
- -> Nested Loop
- -> Seq Scan on int4_tbl
- -> Subquery Scan on ss
- -> Limit
- -> Seq Scan on int8_tbl
- -> Nested Loop Semi Join
- -> Seq Scan on prt1_l_p3_p1 prt1_l_2
- Filter: (c IS NULL)
- -> Nested Loop
- -> Seq Scan on int4_tbl
- -> Subquery Scan on ss_1
- -> Limit
- -> Seq Scan on int8_tbl int8_tbl_1
- -> Nested Loop Semi Join
- -> Seq Scan on prt1_l_p3_p2 prt1_l_3
- Filter: (c IS NULL)
- -> Nested Loop
- -> Seq Scan on int4_tbl
- -> Subquery Scan on ss_2
- -> Limit
- -> Seq Scan on int8_tbl int8_tbl_2
-(28 rows)
+ -> Append
+ -> Seq Scan on prt1_l_p1 prt1_l_1
+ Filter: (c IS NULL)
+ -> Seq Scan on prt1_l_p3_p1 prt1_l_2
+ Filter: (c IS NULL)
+ -> Seq Scan on prt1_l_p3_p2 prt1_l_3
+ Filter: (c IS NULL)
+ -> Materialize
+ -> Nested Loop
+ -> Seq Scan on int4_tbl
+ -> Subquery Scan on ss
+ -> Limit
+ -> Seq Scan on int8_tbl
+(18 rows)
--
-- negative testcases
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index bde29e38a94..c4e827caec3 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2463,74 +2463,43 @@ deallocate ab_q6;
insert into ab values (1,2);
explain (analyze, costs off, summary off, timing off)
update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
- QUERY PLAN
--------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
Update on ab_a1 (actual rows=0 loops=1)
Update on ab_a1_b1 ab_a1_1
Update on ab_a1_b2 ab_a1_2
Update on ab_a1_b3 ab_a1_3
- -> Nested Loop (actual rows=0 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
- Recheck Cond: (a = 1)
- Heap Blocks: exact=1
- -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
- -> Materialize (actual rows=0 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
-> Nested Loop (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
- Recheck Cond: (a = 1)
- Heap Blocks: exact=1
- -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Materialize (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
- -> Nested Loop (actual rows=0 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
- Recheck Cond: (a = 1)
- Heap Blocks: exact=1
- -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
- Index Cond: (a = 1)
- -> Materialize (actual rows=0 loops=1)
-> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-(65 rows)
+ -> Materialize (actual rows=1 loops=1)
+ -> Append (actual rows=1 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
+ Recheck Cond: (a = 1)
+ Heap Blocks: exact=1
+ -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+ Index Cond: (a = 1)
+(34 rows)
table ab;
a | b
@@ -2551,29 +2520,12 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
Update on ab_a1_b3 ab_a1_3
InitPlan 1 (returns $0)
-> Result (actual rows=1 loops=1)
- -> Nested Loop (actual rows=1 loops=1)
- -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
- -> Materialize (actual rows=1 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
- Filter: (b = $0)
- -> Nested Loop (actual rows=1 loops=1)
- -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
- -> Materialize (actual rows=1 loops=1)
- -> Append (actual rows=1 loops=1)
- -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
- Filter: (b = $0)
- -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
- Filter: (b = $0)
- -> Nested Loop (actual rows=1 loops=1)
- -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
- -> Materialize (actual rows=1 loops=1)
+ -> Nested Loop (actual rows=3 loops=1)
+ -> Append (actual rows=3 loops=1)
+ -> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
+ -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
+ -> Materialize (actual rows=1 loops=3)
-> Append (actual rows=1 loops=1)
-> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
Filter: (b = $0)
@@ -2581,7 +2533,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
Filter: (b = $0)
-> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
Filter: (b = $0)
-(36 rows)
+(19 rows)
select tableoid::regclass, * from ab;
tableoid | a | b
@@ -3420,28 +3372,30 @@ explain (costs off) select * from pp_lp where a = 1;
(5 rows)
explain (costs off) update pp_lp set value = 10 where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Update on pp_lp
Update on pp_lp1 pp_lp_1
Update on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
explain (costs off) delete from pp_lp where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Delete on pp_lp
Delete on pp_lp1 pp_lp_1
Delete on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
set constraint_exclusion = 'off'; -- this should not affect the result.
explain (costs off) select * from pp_lp where a = 1;
@@ -3455,28 +3409,30 @@ explain (costs off) select * from pp_lp where a = 1;
(5 rows)
explain (costs off) update pp_lp set value = 10 where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Update on pp_lp
Update on pp_lp1 pp_lp_1
Update on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
explain (costs off) delete from pp_lp where a = 1;
- QUERY PLAN
-----------------------------------
+ QUERY PLAN
+----------------------------------------
Delete on pp_lp
Delete on pp_lp1 pp_lp_1
Delete on pp_lp2 pp_lp_2
- -> Seq Scan on pp_lp1 pp_lp_1
- Filter: (a = 1)
- -> Seq Scan on pp_lp2 pp_lp_2
- Filter: (a = 1)
-(7 rows)
+ -> Append
+ -> Seq Scan on pp_lp1 pp_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on pp_lp2 pp_lp_2
+ Filter: (a = 1)
+(8 rows)
drop table pp_lp;
-- Ensure enable_partition_prune does not affect non-partitioned tables.
@@ -3500,28 +3456,31 @@ explain (costs off) select * from inh_lp where a = 1;
(5 rows)
explain (costs off) update inh_lp set value = 10 where a = 1;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+------------------------------------------------
Update on inh_lp
- Update on inh_lp
- Update on inh_lp1 inh_lp_1
- -> Seq Scan on inh_lp
- Filter: (a = 1)
- -> Seq Scan on inh_lp1 inh_lp_1
- Filter: (a = 1)
-(7 rows)
+ Update on inh_lp inh_lp_1
+ Update on inh_lp1 inh_lp_2
+ -> Result
+ -> Append
+ -> Seq Scan on inh_lp inh_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ Filter: (a = 1)
+(9 rows)
explain (costs off) delete from inh_lp where a = 1;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+------------------------------------------
Delete on inh_lp
- Delete on inh_lp
- Delete on inh_lp1 inh_lp_1
- -> Seq Scan on inh_lp
- Filter: (a = 1)
- -> Seq Scan on inh_lp1 inh_lp_1
- Filter: (a = 1)
-(7 rows)
+ Delete on inh_lp inh_lp_1
+ Delete on inh_lp1 inh_lp_2
+ -> Append
+ -> Seq Scan on inh_lp inh_lp_1
+ Filter: (a = 1)
+ -> Seq Scan on inh_lp1 inh_lp_2
+ Filter: (a = 1)
+(8 rows)
-- Ensure we don't exclude normal relations when we only expect to exclude
-- inheritance children
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 9506aaef82d..b02a6824711 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1632,19 +1632,21 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
--
SET SESSION AUTHORIZATION regress_rls_bob;
EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
- QUERY PLAN
------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------
Update on t1
- Update on t1
- Update on t2 t1_1
- Update on t3 t1_2
- -> Seq Scan on t1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2
- Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+ Update on t1 t1_1
+ Update on t2 t1_2
+ Update on t3 t1_3
+ -> Result
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(12 rows)
UPDATE t1 SET b = b || b WHERE f_leak(b);
NOTICE: f_leak => bbb
@@ -1722,31 +1724,27 @@ NOTICE: f_leak => cde
NOTICE: f_leak => yyyyyy
EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
- QUERY PLAN
------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------
Update on t1
- Update on t1
- Update on t2 t1_1
- Update on t3 t1_2
- -> Nested Loop
- -> Seq Scan on t1
- Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2
- Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
- -> Nested Loop
- -> Seq Scan on t2 t1_1
- Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2
- Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ Update on t1 t1_1
+ Update on t2 t1_2
+ Update on t3 t1_3
-> Nested Loop
- -> Seq Scan on t3 t1_2
- Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2
Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-(19 rows)
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(14 rows)
UPDATE t1 SET b=t1.b FROM t2
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+NOTICE: f_leak => cde
EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
QUERY PLAN
@@ -1795,46 +1793,30 @@ NOTICE: f_leak => cde
EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
- QUERY PLAN
------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------
Update on t1 t1_1
- Update on t1 t1_1
- Update on t2 t1_1_1
- Update on t3 t1_1_2
+ Update on t1 t1_1_1
+ Update on t2 t1_1_2
+ Update on t3 t1_1_3
-> Nested Loop
Join Filter: (t1_1.b = t1_2.b)
- -> Seq Scan on t1 t1_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Append
- -> Seq Scan on t1 t1_2_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_2_2
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2_3
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Nested Loop
- Join Filter: (t1_1_1.b = t1_2.b)
- -> Seq Scan on t2 t1_1_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Append
- -> Seq Scan on t1 t1_2_1
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_2_2
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2_3
+ -> Seq Scan on t1 t1_1_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Nested Loop
- Join Filter: (t1_1_2.b = t1_2.b)
- -> Seq Scan on t3 t1_1_2
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Append
- -> Seq Scan on t1 t1_2_1
+ -> Seq Scan on t2 t1_1_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_2_2
+ -> Seq Scan on t3 t1_1_3
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2_3
- Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-(37 rows)
+ -> Materialize
+ -> Append
+ -> Seq Scan on t1 t1_2_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(21 rows)
UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
@@ -1843,8 +1825,6 @@ NOTICE: f_leak => daddad_updt
NOTICE: f_leak => daddad_updt
NOTICE: f_leak => defdef
NOTICE: f_leak => defdef
-NOTICE: f_leak => daddad_updt
-NOTICE: f_leak => defdef
id | a | b | id | a | b | t1_1 | t1_2
-----+---+-------------+-----+---+-------------+---------------------+---------------------
104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt)
@@ -1880,19 +1860,20 @@ EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
(3 rows)
EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
- QUERY PLAN
------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------
Delete on t1
- Delete on t1
- Delete on t2 t1_1
- Delete on t3 t1_2
- -> Seq Scan on t1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t2 t1_1
- Filter: (((a % 2) = 0) AND f_leak(b))
- -> Seq Scan on t3 t1_2
- Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+ Delete on t1 t1_1
+ Delete on t2 t1_2
+ Delete on t3 t1_3
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(11 rows)
DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
NOTICE: f_leak => bbbbbb_updt
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 24905332b1d..cdff914b93a 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1283,12 +1283,12 @@ SELECT * FROM rw_view1;
(4 rows)
EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
- QUERY PLAN
--------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------
Update on public.base_tbl
Output: base_tbl.a, base_tbl.b
-> Seq Scan on public.base_tbl
- Output: base_tbl.a, (base_tbl.b + 1), base_tbl.ctid
+ Output: (base_tbl.b + 1), base_tbl.ctid
(4 rows)
UPDATE rw_view1 SET b = b + 1 RETURNING *;
@@ -1607,26 +1607,21 @@ UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
QUERY PLAN
-------------------------------------------------------------------------
Update on base_tbl_parent
- Update on base_tbl_parent
- Update on base_tbl_child base_tbl_parent_1
- -> Hash Join
- Hash Cond: (other_tbl_parent.id = base_tbl_parent.a)
- -> Append
- -> Seq Scan on other_tbl_parent other_tbl_parent_1
- -> Seq Scan on other_tbl_child other_tbl_parent_2
- -> Hash
- -> Seq Scan on base_tbl_parent
+ Update on base_tbl_parent base_tbl_parent_1
+ Update on base_tbl_child base_tbl_parent_2
-> Merge Join
- Merge Cond: (base_tbl_parent_1.a = other_tbl_parent.id)
+ Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
-> Sort
- Sort Key: base_tbl_parent_1.a
- -> Seq Scan on base_tbl_child base_tbl_parent_1
+ Sort Key: base_tbl_parent.a
+ -> Append
+ -> Seq Scan on base_tbl_parent base_tbl_parent_1
+ -> Seq Scan on base_tbl_child base_tbl_parent_2
-> Sort
Sort Key: other_tbl_parent.id
-> Append
-> Seq Scan on other_tbl_parent other_tbl_parent_1
-> Seq Scan on other_tbl_child other_tbl_parent_2
-(20 rows)
+(15 rows)
UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
@@ -2332,36 +2327,39 @@ SELECT * FROM v1 WHERE a=8;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
- QUERY PLAN
------------------------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------
Update on public.t1
- Update on public.t1
- Update on public.t11 t1_1
- Update on public.t12 t1_2
- Update on public.t111 t1_3
- -> Index Scan using t1_a_idx on public.t1
- Output: 100, t1.b, t1.c, t1.ctid
- Index Cond: ((t1.a > 5) AND (t1.a < 7))
- Filter: ((t1.a <> 6) AND (SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
- SubPlan 1
- -> Append
- -> Seq Scan on public.t12 t12_1
- Filter: (t12_1.a = t1.a)
- -> Seq Scan on public.t111 t12_2
- Filter: (t12_2.a = t1.a)
- -> Index Scan using t11_a_idx on public.t11 t1_1
- Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
- Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
- Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
- -> Index Scan using t12_a_idx on public.t12 t1_2
- Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
- Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
- Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
- -> Index Scan using t111_a_idx on public.t111 t1_3
- Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
- Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
- Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(27 rows)
+ Update on public.t1 t1_1
+ Update on public.t11 t1_2
+ Update on public.t12 t1_3
+ Update on public.t111 t1_4
+ -> Result
+ Output: 100, t1.tableoid, t1.ctid
+ -> Append
+ -> Index Scan using t1_a_idx on public.t1 t1_1
+ Output: t1_1.tableoid, t1_1.ctid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+ Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1_1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1_1.a)
+ -> Index Scan using t11_a_idx on public.t11 t1_2
+ Output: t1_2.tableoid, t1_2.ctid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+ Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_3
+ Output: t1_3.tableoid, t1_3.ctid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+ Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_4
+ Output: t1_4.tableoid, t1_4.ctid
+ Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
+ Filter: ((t1_4.a <> 6) AND (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(30 rows)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2376,36 +2374,39 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
- QUERY PLAN
--------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------------
Update on public.t1
- Update on public.t1
- Update on public.t11 t1_1
- Update on public.t12 t1_2
- Update on public.t111 t1_3
- -> Index Scan using t1_a_idx on public.t1
- Output: (t1.a + 1), t1.b, t1.c, t1.ctid
- Index Cond: ((t1.a > 5) AND (t1.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
- SubPlan 1
- -> Append
- -> Seq Scan on public.t12 t12_1
- Filter: (t12_1.a = t1.a)
- -> Seq Scan on public.t111 t12_2
- Filter: (t12_2.a = t1.a)
- -> Index Scan using t11_a_idx on public.t11 t1_1
- Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
- Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
- -> Index Scan using t12_a_idx on public.t12 t1_2
- Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
- Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
- -> Index Scan using t111_a_idx on public.t111 t1_3
- Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
- Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(27 rows)
+ Update on public.t1 t1_1
+ Update on public.t11 t1_2
+ Update on public.t12 t1_3
+ Update on public.t111 t1_4
+ -> Result
+ Output: (t1.a + 1), t1.tableoid, t1.ctid
+ -> Append
+ -> Index Scan using t1_a_idx on public.t1 t1_1
+ Output: t1_1.a, t1_1.tableoid, t1_1.ctid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1_1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1_1.a)
+ -> Index Scan using t11_a_idx on public.t11 t1_2
+ Output: t1_2.a, t1_2.tableoid, t1_2.ctid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_3
+ Output: t1_3.a, t1_3.tableoid, t1_3.ctid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_4
+ Output: t1_4.a, t1_4.tableoid, t1_4.ctid
+ Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
+ Filter: ((SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(30 rows)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
NOTICE: snooped value: 8
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d79f60..dc34ac67b37 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -172,14 +172,14 @@ EXPLAIN (VERBOSE, COSTS OFF)
UPDATE update_test t
SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
WHERE CURRENT_USER = SESSION_USER;
- QUERY PLAN
-------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------
Update on public.update_test t
-> Result
- Output: $1, $2, t.c, (SubPlan 1 (returns $1,$2)), t.ctid
+ Output: $1, $2, (SubPlan 1 (returns $1,$2)), t.ctid
One-Time Filter: (CURRENT_USER = SESSION_USER)
-> Seq Scan on public.update_test t
- Output: t.c, t.a, t.ctid
+ Output: t.a, t.ctid
SubPlan 1 (returns $1,$2)
-> Seq Scan on public.update_test s
Output: s.b, s.a
@@ -308,8 +308,8 @@ ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO
-- The order of subplans should be in bound order
EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
- QUERY PLAN
--------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------
Update on range_parted
Update on part_a_1_a_10 range_parted_1
Update on part_a_10_a_20 range_parted_2
@@ -318,21 +318,22 @@ EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
Update on part_d_1_15 range_parted_5
Update on part_d_15_20 range_parted_6
Update on part_b_20_b_30 range_parted_7
- -> Seq Scan on part_a_1_a_10 range_parted_1
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_a_10_a_20 range_parted_2
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_b_1_b_10 range_parted_3
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_c_1_100 range_parted_4
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_d_1_15 range_parted_5
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_d_15_20 range_parted_6
- Filter: (c > '97'::numeric)
- -> Seq Scan on part_b_20_b_30 range_parted_7
- Filter: (c > '97'::numeric)
-(22 rows)
+ -> Append
+ -> Seq Scan on part_a_1_a_10 range_parted_1
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_a_10_a_20 range_parted_2
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_b_1_b_10 range_parted_3
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_c_1_100 range_parted_4
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_d_1_15 range_parted_5
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_d_15_20 range_parted_6
+ Filter: (c > '97'::numeric)
+ -> Seq Scan on part_b_20_b_30 range_parted_7
+ Filter: (c > '97'::numeric)
+(23 rows)
-- fail, row movement happens only within the partition subtree.
UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 9a6b716ddcc..0affacc1915 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -2906,47 +2906,35 @@ SELECT * FROM parent;
EXPLAIN (VERBOSE, COSTS OFF)
WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
DELETE FROM a USING wcte WHERE aa = q2;
- QUERY PLAN
-----------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------
Delete on public.a
- Delete on public.a
- Delete on public.b a_1
- Delete on public.c a_2
- Delete on public.d a_3
+ Delete on public.a a_1
+ Delete on public.b a_2
+ Delete on public.c a_3
+ Delete on public.d a_4
CTE wcte
-> Insert on public.int8_tbl
Output: int8_tbl.q2
-> Result
Output: '42'::bigint, '47'::bigint
- -> Nested Loop
- Output: a.ctid, wcte.*
- Join Filter: (a.aa = wcte.q2)
- -> Seq Scan on public.a
- Output: a.ctid, a.aa
- -> CTE Scan on wcte
+ -> Hash Join
+ Output: wcte.*, a.tableoid, a.ctid
+ Hash Cond: (a.aa = wcte.q2)
+ -> Append
+ -> Seq Scan on public.a a_1
+ Output: a_1.aa, a_1.tableoid, a_1.ctid
+ -> Seq Scan on public.b a_2
+ Output: a_2.aa, a_2.tableoid, a_2.ctid
+ -> Seq Scan on public.c a_3
+ Output: a_3.aa, a_3.tableoid, a_3.ctid
+ -> Seq Scan on public.d a_4
+ Output: a_4.aa, a_4.tableoid, a_4.ctid
+ -> Hash
Output: wcte.*, wcte.q2
- -> Nested Loop
- Output: a_1.ctid, wcte.*
- Join Filter: (a_1.aa = wcte.q2)
- -> Seq Scan on public.b a_1
- Output: a_1.ctid, a_1.aa
- -> CTE Scan on wcte
- Output: wcte.*, wcte.q2
- -> Nested Loop
- Output: a_2.ctid, wcte.*
- Join Filter: (a_2.aa = wcte.q2)
- -> Seq Scan on public.c a_2
- Output: a_2.ctid, a_2.aa
- -> CTE Scan on wcte
- Output: wcte.*, wcte.q2
- -> Nested Loop
- Output: a_3.ctid, wcte.*
- Join Filter: (a_3.aa = wcte.q2)
- -> Seq Scan on public.d a_3
- Output: a_3.ctid, a_3.aa
- -> CTE Scan on wcte
- Output: wcte.*, wcte.q2
-(38 rows)
+ -> CTE Scan on wcte
+ Output: wcte.*, wcte.q2
+(26 rows)
-- error cases
-- data-modifying WITH tries to use its own output