mirror of
https://github.com/postgres/postgres.git
synced 2025-06-27 23:21:58 +03:00
Revise child-to-root tuple conversion map management.
Store the tuple conversion map to convert a tuple from a child table's format to the root format in a new ri_ChildToRootMap field in ResultRelInfo. It is initialized if transition tuple capture for FOR STATEMENT triggers or INSERT tuple routing on a partitioned table is needed. Previously, ModifyTable kept the maps in the per-subplan ModifyTableState->mt_per_subplan_tupconv_maps array, or when tuple routing was used, in ResultRelInfo->ri_Partitioninfo->pi_PartitionToRootMap. The new field replaces both of those. Now that the child-to-root tuple conversion map is always available in ResultRelInfo (when needed), remove the TransitionCaptureState.tcs_map field. The callers of Exec*Trigger() functions no longer need to set or save it, which is much less confusing and bug-prone. Also, as a future optimization, this will allow us to delay creating the map for a given result relation until the relation is actually processed during execution. Author: Amit Langote Discussion: https://www.postgresql.org/message-id/CA%2BHiwqHtCWLdK-LO%3DNEsvOdHx%2B7yv4mE_zYK0i3BH7dXb-wxog%40mail.gmail.com
This commit is contained in:
@ -72,9 +72,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
||||
ResultRelInfo *targetRelInfo,
|
||||
TupleTableSlot *slot,
|
||||
ResultRelInfo **partRelInfo);
|
||||
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
|
||||
static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
|
||||
int whichplan);
|
||||
|
||||
/*
|
||||
* Verify that the tuples to be produced by INSERT or UPDATE match the
|
||||
@ -1086,9 +1083,7 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
|
||||
{
|
||||
EState *estate = mtstate->ps.state;
|
||||
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
|
||||
int map_index;
|
||||
TupleConversionMap *tupconv_map;
|
||||
TupleConversionMap *saved_tcs_map = NULL;
|
||||
bool tuple_deleted;
|
||||
TupleTableSlot *epqslot = NULL;
|
||||
|
||||
@ -1163,37 +1158,25 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
|
||||
|
||||
/*
|
||||
* resultRelInfo is one of the per-subplan resultRelInfos. So we should
|
||||
* convert the tuple into root's tuple descriptor, since ExecInsert()
|
||||
* starts the search from root. The tuple conversion map list is in the
|
||||
* order of mtstate->resultRelInfo[], so to retrieve the one for this
|
||||
* resultRel, we need to know the position of the resultRel in
|
||||
* mtstate->resultRelInfo[].
|
||||
* convert the tuple into root's tuple descriptor if needed, since
|
||||
* ExecInsert() starts the search from root.
|
||||
*/
|
||||
map_index = resultRelInfo - mtstate->resultRelInfo;
|
||||
Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
|
||||
tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
|
||||
tupconv_map = resultRelInfo->ri_ChildToRootMap;
|
||||
if (tupconv_map != NULL)
|
||||
slot = execute_attr_map_slot(tupconv_map->attrMap,
|
||||
slot,
|
||||
mtstate->mt_root_tuple_slot);
|
||||
|
||||
/*
|
||||
* ExecInsert() may scribble on mtstate->mt_transition_capture, so save
|
||||
* the currently active map.
|
||||
*/
|
||||
if (mtstate->mt_transition_capture)
|
||||
saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
|
||||
|
||||
/* Tuple routing starts from the root table. */
|
||||
*inserted_tuple = ExecInsert(mtstate, mtstate->rootResultRelInfo, slot,
|
||||
planSlot, estate, canSetTag);
|
||||
|
||||
/* Clear the INSERT's tuple and restore the saved map. */
|
||||
/*
|
||||
* Reset the transition state that may possibly have been written
|
||||
* by INSERT.
|
||||
*/
|
||||
if (mtstate->mt_transition_capture)
|
||||
{
|
||||
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
|
||||
mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
|
||||
}
|
||||
|
||||
/* We're done moving. */
|
||||
return true;
|
||||
@ -1870,28 +1853,6 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
|
||||
MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
|
||||
RelationGetRelid(targetRelInfo->ri_RelationDesc),
|
||||
CMD_UPDATE);
|
||||
|
||||
/*
|
||||
* If we found that we need to collect transition tuples then we may also
|
||||
* need tuple conversion maps for any children that have TupleDescs that
|
||||
* aren't compatible with the tuplestores. (We can share these maps
|
||||
* between the regular and ON CONFLICT cases.)
|
||||
*/
|
||||
if (mtstate->mt_transition_capture != NULL ||
|
||||
mtstate->mt_oc_transition_capture != NULL)
|
||||
{
|
||||
ExecSetupChildParentMapForSubplan(mtstate);
|
||||
|
||||
/*
|
||||
* Install the conversion map for the first plan for UPDATE and DELETE
|
||||
* operations. It will be advanced each time we switch to the next
|
||||
* plan. (INSERT operations set it every time, so we need not update
|
||||
* mtstate->mt_oc_transition_capture here.)
|
||||
*/
|
||||
if (mtstate->mt_transition_capture && mtstate->operation != CMD_INSERT)
|
||||
mtstate->mt_transition_capture->tcs_map =
|
||||
tupconv_map_for_subplan(mtstate, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1929,35 +1890,20 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
||||
|
||||
/*
|
||||
* If we're capturing transition tuples, we might need to convert from the
|
||||
* partition rowtype to root partitioned table's rowtype.
|
||||
* partition rowtype to root partitioned table's rowtype. But if there
|
||||
* are no BEFORE triggers on the partition that could change the tuple, we
|
||||
* can just remember the original unconverted tuple to avoid a needless
|
||||
* round trip conversion.
|
||||
*/
|
||||
if (mtstate->mt_transition_capture != NULL)
|
||||
{
|
||||
if (partrel->ri_TrigDesc &&
|
||||
partrel->ri_TrigDesc->trig_insert_before_row)
|
||||
{
|
||||
/*
|
||||
* If there are any BEFORE triggers on the partition, we'll have
|
||||
* to be ready to convert their result back to tuplestore format.
|
||||
*/
|
||||
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
|
||||
mtstate->mt_transition_capture->tcs_map =
|
||||
partrouteinfo->pi_PartitionToRootMap;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Otherwise, just remember the original unconverted tuple, to
|
||||
* avoid a needless round trip conversion.
|
||||
*/
|
||||
mtstate->mt_transition_capture->tcs_original_insert_tuple = slot;
|
||||
mtstate->mt_transition_capture->tcs_map = NULL;
|
||||
}
|
||||
}
|
||||
if (mtstate->mt_oc_transition_capture != NULL)
|
||||
{
|
||||
mtstate->mt_oc_transition_capture->tcs_map =
|
||||
partrouteinfo->pi_PartitionToRootMap;
|
||||
bool has_before_insert_row_trig;
|
||||
|
||||
has_before_insert_row_trig = (partrel->ri_TrigDesc &&
|
||||
partrel->ri_TrigDesc->trig_insert_before_row);
|
||||
|
||||
mtstate->mt_transition_capture->tcs_original_insert_tuple =
|
||||
!has_before_insert_row_trig ? slot : NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1975,58 +1921,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the child-to-root tuple conversion map array for UPDATE subplans.
|
||||
*
|
||||
* This map array is required to convert the tuple from the subplan result rel
|
||||
* to the target table descriptor. This requirement arises for two independent
|
||||
* scenarios:
|
||||
* 1. For update-tuple-routing.
|
||||
* 2. For capturing tuples in transition tables.
|
||||
*/
|
||||
static void
|
||||
ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
|
||||
{
|
||||
ResultRelInfo *targetRelInfo = mtstate->rootResultRelInfo;
|
||||
ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
|
||||
TupleDesc outdesc;
|
||||
int numResultRelInfos = mtstate->mt_nplans;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Build array of conversion maps from each child's TupleDesc to the one
|
||||
* used in the target relation. The map pointers may be NULL when no
|
||||
* conversion is necessary, which is hopefully a common case.
|
||||
*/
|
||||
|
||||
/* Get tuple descriptor of the target rel. */
|
||||
outdesc = RelationGetDescr(targetRelInfo->ri_RelationDesc);
|
||||
|
||||
mtstate->mt_per_subplan_tupconv_maps = (TupleConversionMap **)
|
||||
palloc(sizeof(TupleConversionMap *) * numResultRelInfos);
|
||||
|
||||
for (i = 0; i < numResultRelInfos; ++i)
|
||||
{
|
||||
mtstate->mt_per_subplan_tupconv_maps[i] =
|
||||
convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
|
||||
outdesc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For a given subplan index, get the tuple conversion map.
|
||||
*/
|
||||
static TupleConversionMap *
|
||||
tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
|
||||
{
|
||||
/* If nobody else set the per-subplan array of maps, do so ourselves. */
|
||||
if (mtstate->mt_per_subplan_tupconv_maps == NULL)
|
||||
ExecSetupChildParentMapForSubplan(mtstate);
|
||||
|
||||
Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
|
||||
return mtstate->mt_per_subplan_tupconv_maps[whichplan];
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecModifyTable
|
||||
*
|
||||
@ -2122,17 +2016,6 @@ ExecModifyTable(PlanState *pstate)
|
||||
junkfilter = resultRelInfo->ri_junkFilter;
|
||||
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
|
||||
node->mt_arowmarks[node->mt_whichplan]);
|
||||
/* Prepare to convert transition tuples from this child. */
|
||||
if (node->mt_transition_capture != NULL)
|
||||
{
|
||||
node->mt_transition_capture->tcs_map =
|
||||
tupconv_map_for_subplan(node, node->mt_whichplan);
|
||||
}
|
||||
if (node->mt_oc_transition_capture != NULL)
|
||||
{
|
||||
node->mt_oc_transition_capture->tcs_map =
|
||||
tupconv_map_for_subplan(node, node->mt_whichplan);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@ -2334,8 +2217,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
* If it's a partitioned table, the root partition doesn't appear
|
||||
* elsewhere in the plan and its RT index is given explicitly in
|
||||
* node->rootRelation. Otherwise (i.e. table inheritance) the target
|
||||
* relation is the first relation in the node->resultRelations list, and
|
||||
* we will initialize it in the loop below.
|
||||
* relation is the first relation in the node->resultRelations list.
|
||||
*----------
|
||||
*/
|
||||
if (node->rootRelation > 0)
|
||||
@ -2347,6 +2229,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
else
|
||||
{
|
||||
mtstate->rootResultRelInfo = mtstate->resultRelInfo;
|
||||
ExecInitResultRelation(estate, mtstate->resultRelInfo,
|
||||
linitial_int(node->resultRelations));
|
||||
}
|
||||
|
||||
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
|
||||
@ -2356,6 +2240,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
|
||||
mtstate->fireBSTriggers = true;
|
||||
|
||||
/*
|
||||
* Build state for collecting transition tuples. This requires having a
|
||||
* valid trigger query context, so skip it in explain-only mode.
|
||||
*/
|
||||
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
|
||||
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
|
||||
@ -2370,8 +2261,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
|
||||
subplan = (Plan *) lfirst(l1);
|
||||
|
||||
/* This opens the relation and fills ResultRelInfo. */
|
||||
ExecInitResultRelation(estate, resultRelInfo, resultRelation);
|
||||
/*
|
||||
* This opens result relation and fills ResultRelInfo. (root relation
|
||||
* was initialized already.)
|
||||
*/
|
||||
if (resultRelInfo != mtstate->rootResultRelInfo)
|
||||
ExecInitResultRelation(estate, resultRelInfo, resultRelation);
|
||||
|
||||
/* Initialize the usesFdwDirectModify flag */
|
||||
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
|
||||
@ -2427,6 +2322,23 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
eflags);
|
||||
}
|
||||
|
||||
/*
|
||||
* If needed, initialize a map to convert tuples in the child format
|
||||
* to the format of the table mentioned in the query (root relation).
|
||||
* It's needed for update tuple routing, because the routing starts
|
||||
* from the root relation. It's also needed for capturing transition
|
||||
* tuples, because the transition tuple store can only store tuples
|
||||
* in the root table format.
|
||||
*
|
||||
* For INSERT, the map is only initialized for a given partition when
|
||||
* the partition itself is first initialized by ExecFindPartition().
|
||||
*/
|
||||
if (update_tuple_routing_needed ||
|
||||
(mtstate->mt_transition_capture &&
|
||||
mtstate->operation != CMD_INSERT))
|
||||
resultRelInfo->ri_ChildToRootMap =
|
||||
convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
|
||||
RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc));
|
||||
resultRelInfo++;
|
||||
i++;
|
||||
}
|
||||
@ -2451,26 +2363,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||
ExecSetupPartitionTupleRouting(estate, mtstate, rel);
|
||||
|
||||
/*
|
||||
* Build state for collecting transition tuples. This requires having a
|
||||
* valid trigger query context, so skip it in explain-only mode.
|
||||
*/
|
||||
if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
|
||||
ExecSetupTransitionCaptureState(mtstate, estate);
|
||||
|
||||
/*
|
||||
* Construct mapping from each of the per-subplan partition attnos to the
|
||||
* root attno. This is required when during update row movement the tuple
|
||||
* descriptor of a source partition does not match the root partitioned
|
||||
* table descriptor. In such a case we need to convert tuples to the root
|
||||
* tuple descriptor, because the search for destination partition starts
|
||||
* from the root. We'll also need a slot to store these converted tuples.
|
||||
* We can skip this setup if it's not a partition key update.
|
||||
* For update row movement we'll need a dedicated slot to store the
|
||||
* tuples that have been converted from partition format to the root
|
||||
* table format.
|
||||
*/
|
||||
if (update_tuple_routing_needed)
|
||||
{
|
||||
ExecSetupChildParentMapForSubplan(mtstate);
|
||||
mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize any WITH CHECK OPTION constraints if needed.
|
||||
|
Reference in New Issue
Block a user