Commit a1115fa0 authored by Tom Lane's avatar Tom Lane

Postpone some more stuff out of ExecInitModifyTable.

Delay creation of the projections for INSERT and UPDATE tuples
until they're needed.  This saves a pretty fair amount of work
when only some of the partitions are actually touched.

The logic associated with identifying junk columns in UPDATE/DELETE
is moved to another loop, allowing removal of one loop over the
target relations; but it didn't actually change at all.

Extracted from a larger patch, which seemed to me to be too messy
to push in one commit.

Amit Langote, reviewed at different times by Heikki Linnakangas and
myself

Discussion: https://postgr.es/m/CA+HiwqG7ZruBmmih3wPsBZ4s0H2EhywrnXEduckY5Hr3fWzPWA@mail.gmail.com
parent 3b82d990
...@@ -1221,6 +1221,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, ...@@ -1221,6 +1221,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_projectNew = NULL; resultRelInfo->ri_projectNew = NULL;
resultRelInfo->ri_newTupleSlot = NULL; resultRelInfo->ri_newTupleSlot = NULL;
resultRelInfo->ri_oldTupleSlot = NULL; resultRelInfo->ri_oldTupleSlot = NULL;
resultRelInfo->ri_projectNewInfoValid = false;
resultRelInfo->ri_FdwState = NULL; resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_usesFdwDirectModify = false; resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_ConstraintExprs = NULL;
......
...@@ -371,6 +371,139 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, ...@@ -371,6 +371,139 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
MemoryContextSwitchTo(oldContext); MemoryContextSwitchTo(oldContext);
} }
/*
* ExecInitInsertProjection
* Do one-time initialization of projection data for INSERT tuples.
*
* INSERT queries may need a projection to filter out junk attrs in the tlist.
*
* This is "one-time" for any given result rel, but we might touch
* more than one result rel in the course of a partitioned INSERT.
*
* This is also a convenient place to verify that the
* output of an INSERT matches the target table.
*/
static void
ExecInitInsertProjection(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo)
{
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Plan *subplan = outerPlan(node);
EState *estate = mtstate->ps.state;
List *insertTargetList = NIL;
bool need_projection = false;
ListCell *l;
/* Extract non-junk columns of the subplan's result tlist. */
foreach(l, subplan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (!tle->resjunk)
insertTargetList = lappend(insertTargetList, tle);
else
need_projection = true;
}
/*
* The junk-free list must produce a tuple suitable for the result
* relation.
*/
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, insertTargetList);
/* We'll need a slot matching the table's format. */
resultRelInfo->ri_newTupleSlot =
table_slot_create(resultRelInfo->ri_RelationDesc,
&estate->es_tupleTable);
/* 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);
}
resultRelInfo->ri_projectNewInfoValid = true;
}
/*
* ExecInitUpdateProjection
* Do one-time initialization of projection data for UPDATE tuples.
*
* 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.
*
* This is "one-time" for any given result rel, but we might touch more than
* one result rel in the course of a partitioned UPDATE, and each one needs
* its own projection due to possible column order variation.
*
* This is also a convenient place to verify that the output of an UPDATE
* matches the target table (ExecBuildUpdateProjection does that).
*/
static void
ExecInitUpdateProjection(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo)
{
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
Plan *subplan = outerPlan(node);
EState *estate = mtstate->ps.state;
TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
int whichrel;
List *updateColnos;
/*
* Usually, mt_lastResultIndex matches the target rel. If it happens not
* to, we can get the index the hard way with an integer division.
*/
whichrel = mtstate->mt_lastResultIndex;
if (resultRelInfo != mtstate->resultRelInfo + whichrel)
{
whichrel = resultRelInfo - mtstate->resultRelInfo;
Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
}
updateColnos = (List *) list_nth(node->updateColnosLists, whichrel);
/*
* For UPDATE, we use the old tuple to fill up missing values in the tuple
* produced by the subplan 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,
&estate->es_tupleTable);
resultRelInfo->ri_newTupleSlot =
table_slot_create(resultRelInfo->ri_RelationDesc,
&estate->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);
resultRelInfo->ri_projectNewInfoValid = true;
}
/* /*
* ExecGetInsertNewTuple * ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be inserted into given result * This prepares a "new" tuple ready to be inserted into given result
...@@ -429,6 +562,8 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo, ...@@ -429,6 +562,8 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
ProjectionInfo *newProj = relinfo->ri_projectNew; ProjectionInfo *newProj = relinfo->ri_projectNew;
ExprContext *econtext; ExprContext *econtext;
/* Use a few extra Asserts to protect against outside callers */
Assert(relinfo->ri_projectNewInfoValid);
Assert(planSlot != NULL && !TTS_EMPTY(planSlot)); Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot)); Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
...@@ -1375,8 +1510,12 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, ...@@ -1375,8 +1510,12 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
else else
{ {
/* Fetch the most recent version of old tuple. */ /* Fetch the most recent version of old tuple. */
TupleTableSlot *oldSlot = resultRelInfo->ri_oldTupleSlot; TupleTableSlot *oldSlot;
/* ... but first, make sure ri_oldTupleSlot is initialized. */
if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
ExecInitUpdateProjection(mtstate, resultRelInfo);
oldSlot = resultRelInfo->ri_oldTupleSlot;
if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
tupleid, tupleid,
SnapshotAny, SnapshotAny,
...@@ -1706,6 +1845,10 @@ lreplace:; ...@@ -1706,6 +1845,10 @@ lreplace:;
/* Tuple not passing quals anymore, exiting... */ /* Tuple not passing quals anymore, exiting... */
return NULL; return NULL;
/* Make sure ri_oldTupleSlot is initialized. */
if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
ExecInitUpdateProjection(mtstate, resultRelInfo);
/* Fetch the most recent version of old tuple. */ /* Fetch the most recent version of old tuple. */
oldSlot = resultRelInfo->ri_oldTupleSlot; oldSlot = resultRelInfo->ri_oldTupleSlot;
if (!table_tuple_fetch_row_version(resultRelationDesc, if (!table_tuple_fetch_row_version(resultRelationDesc,
...@@ -2388,11 +2531,17 @@ ExecModifyTable(PlanState *pstate) ...@@ -2388,11 +2531,17 @@ ExecModifyTable(PlanState *pstate)
switch (operation) switch (operation)
{ {
case CMD_INSERT: case CMD_INSERT:
/* Initialize projection info if first time for this table */
if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
ExecInitInsertProjection(node, resultRelInfo);
slot = ExecGetInsertNewTuple(resultRelInfo, planSlot); slot = ExecGetInsertNewTuple(resultRelInfo, planSlot);
slot = ExecInsert(node, resultRelInfo, slot, planSlot, slot = ExecInsert(node, resultRelInfo, slot, planSlot,
estate, node->canSetTag); estate, node->canSetTag);
break; break;
case CMD_UPDATE: case CMD_UPDATE:
/* Initialize projection info if first time for this table */
if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
ExecInitUpdateProjection(node, resultRelInfo);
/* /*
* Make the new tuple by combining plan's output tuple with * Make the new tuple by combining plan's output tuple with
...@@ -2665,8 +2814,65 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ...@@ -2665,8 +2814,65 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i, i,
eflags); eflags);
} }
/*
* 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");
}
}
} }
/*
* 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 */
/* Get the root target relation */ /* Get the root target relation */
rel = mtstate->rootResultRelInfo->ri_RelationDesc; rel = mtstate->rootResultRelInfo->ri_RelationDesc;
...@@ -2842,163 +3048,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ...@@ -2842,163 +3048,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks); EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
/*
* 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
* 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++)
{
resultRelInfo = &mtstate->resultRelInfo[i];
/*
* Prepare to generate tuples suitable for the target relation.
*/
if (operation == CMD_INSERT)
{
List *insertTargetList = NIL;
bool need_projection = false;
foreach(l, subplan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (!tle->resjunk)
insertTargetList = lappend(insertTargetList, tle);
else
need_projection = true;
}
/*
* The junk-free list must produce a tuple suitable for the result
* relation.
*/
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
insertTargetList);
/* We'll need a slot matching the table's format. */
resultRelInfo->ri_newTupleSlot =
table_slot_create(resultRelInfo->ri_RelationDesc,
&mtstate->ps.state->es_tupleTable);
/* 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);
updateColnos = (List *) list_nth(node->updateColnosLists, i);
/*
* 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");
}
}
}
/*
* 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 * 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. * lookups. If there are not a lot, a simple linear search is faster.
......
...@@ -431,6 +431,8 @@ typedef struct ResultRelInfo ...@@ -431,6 +431,8 @@ typedef struct ResultRelInfo
TupleTableSlot *ri_newTupleSlot; TupleTableSlot *ri_newTupleSlot;
/* Slot to hold the old tuple being updated */ /* Slot to hold the old tuple being updated */
TupleTableSlot *ri_oldTupleSlot; TupleTableSlot *ri_oldTupleSlot;
/* Have the projection and the slots above been initialized? */
bool ri_projectNewInfoValid;
/* triggers to be fired, if any */ /* triggers to be fired, if any */
TriggerDesc *ri_TrigDesc; TriggerDesc *ri_TrigDesc;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment