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:
Heikki Linnakangas 2020-10-19 14:11:54 +03:00
parent f49b85d783
commit 6973533650
8 changed files with 84 additions and 215 deletions

View File

@ -3106,31 +3106,14 @@ CopyFrom(CopyState cstate)
/*
* If we're capturing transition tuples, we might need to convert
* from the partition rowtype to root rowtype.
* from the partition rowtype to root 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 (cstate->transition_capture != NULL)
{
if (has_before_insert_row_trig)
{
/*
* If there are any BEFORE triggers on the partition,
* we'll have to be ready to convert their result back to
* tuplestore format.
*/
cstate->transition_capture->tcs_original_insert_tuple = NULL;
cstate->transition_capture->tcs_map =
resultRelInfo->ri_PartitionInfo->pi_PartitionToRootMap;
}
else
{
/*
* Otherwise, just remember the original unconverted
* tuple, to avoid a needless round trip conversion.
*/
cstate->transition_capture->tcs_original_insert_tuple = myslot;
cstate->transition_capture->tcs_map = NULL;
}
}
cstate->transition_capture->tcs_original_insert_tuple =
!has_before_insert_row_trig ? myslot : NULL;
/*
* We might need to convert from the root rowtype to the partition

View File

@ -35,6 +35,7 @@
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/execPartition.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
@ -4292,9 +4293,10 @@ GetAfterTriggersTableData(Oid relid, CmdType cmdType)
* If there are no triggers in 'trigdesc' that request relevant transition
* tables, then return NULL.
*
* The resulting object can be passed to the ExecAR* functions. The caller
* should set tcs_map or tcs_original_insert_tuple as appropriate when dealing
* with child tables.
* The resulting object can be passed to the ExecAR* functions. When
* dealing with child tables, the caller can set tcs_original_insert_tuple
* to avoid having to reconstruct the original tuple in the root table's
* format.
*
* Note that we copy the flags from a parent table into this struct (rather
* than subsequently using the relation's TriggerDesc directly) so that we can
@ -5389,7 +5391,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
if (row_trigger && transition_capture != NULL)
{
TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
TupleConversionMap *map = transition_capture->tcs_map;
TupleConversionMap *map = relinfo->ri_ChildToRootMap;
bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = transition_capture->tcs_update_new_table;

View File

@ -1244,6 +1244,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_TrigNewSlot = NULL;
resultRelInfo->ri_PartitionRoot = partition_root;
resultRelInfo->ri_PartitionInfo = NULL; /* may be set later */
resultRelInfo->ri_ChildToRootMap = NULL;
resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
}

View File

@ -907,6 +907,15 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
}
}
/*
* Also, if transition capture is required, store a map to convert tuples
* from partition's rowtype to the root partition table's.
*/
if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
leaf_part_rri->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
RelationGetDescr(leaf_part_rri->ri_PartitionRoot));
/*
* Since we've just initialized this ResultRelInfo, it's not in any list
* attached to the estate as yet. Add it, so that it can be found later.
@ -976,20 +985,6 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
else
partrouteinfo->pi_PartitionTupleSlot = NULL;
/*
* Also, if transition capture is required, store a map to convert tuples
* from partition's rowtype to the root partition table's.
*/
if (mtstate &&
(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
{
partrouteinfo->pi_PartitionToRootMap =
convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
RelationGetDescr(partRelInfo->ri_PartitionRoot));
}
else
partrouteinfo->pi_PartitionToRootMap = NULL;
/*
* If the partition is a foreign table, let the FDW init itself for
* routing tuples to the partition.

View File

@ -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.

View File

@ -46,7 +46,7 @@ typedef struct TriggerData
* The state for capturing old and new tuples into transition tables for a
* single ModifyTable node (or other operation source, e.g. copy.c).
*
* This is per-caller to avoid conflicts in setting tcs_map or
* This is per-caller to avoid conflicts in setting
* tcs_original_insert_tuple. Note, however, that the pointed-to
* private data may be shared across multiple callers.
*/
@ -65,14 +65,6 @@ typedef struct TransitionCaptureState
bool tcs_update_new_table;
bool tcs_insert_new_table;
/*
* For UPDATE and DELETE, AfterTriggerSaveEvent may need to convert the
* new and old tuples from a child table's format to the format of the
* relation named in a query so that it is compatible with the transition
* tuplestores. The caller must store the conversion map here if so.
*/
TupleConversionMap *tcs_map;
/*
* For INSERT and COPY, it would be wasteful to convert tuples from child
* format to parent format after they have already been converted in the

View File

@ -36,12 +36,6 @@ typedef struct PartitionRoutingInfo
*/
TupleConversionMap *pi_RootToPartitionMap;
/*
* Map for converting tuples in partition format into the root partitioned
* table format, or NULL if no conversion is required.
*/
TupleConversionMap *pi_PartitionToRootMap;
/*
* Slot to store tuples in partition format, or NULL when no translation
* is required between root and partition.

View File

@ -486,6 +486,13 @@ typedef struct ResultRelInfo
/* info for partition tuple routing (NULL if not set up yet) */
struct PartitionRoutingInfo *ri_PartitionInfo;
/*
* Map to convert child result relation tuples to the format of the table
* actually mentioned in the query (called "root"). Set only if
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
/* for use by copy.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
} ResultRelInfo;
@ -1179,9 +1186,6 @@ typedef struct ModifyTableState
/* controls transition table population for INSERT...ON CONFLICT UPDATE */
struct TransitionCaptureState *mt_oc_transition_capture;
/* Per plan map for tuple conversion from child to root */
TupleConversionMap **mt_per_subplan_tupconv_maps;
} ModifyTableState;
/* ----------------