diff options
Diffstat (limited to 'src')
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 |