From 82a386601239c90da2a4bba69762df28351053cb Mon Sep 17 00:00:00 2001 From: amit Date: Tue, 30 Jul 2019 10:51:35 +0900 Subject: [PATCH v9 4/4] Refactor transition tuple capture code a bit In the case of inherited update and partitioned table inserts, a child tuple needs to be converted back into the root table format. The tuple conversion map needed to do that was previously stored in ModifyTableState and adjusted every time the child relation changed, an arrangement which is a bit cumbersome to maintain. Instead save the map in the child result relation's ResultRelInfo. This allows to get rid of a bunch of code that was needed to manipulate tcs_map. --- src/backend/commands/copy.c | 31 ++--- src/backend/commands/trigger.c | 19 ++- src/backend/executor/execPartition.c | 19 ++- src/backend/executor/nodeModifyTable.c | 207 +++++++-------------------------- src/include/commands/trigger.h | 10 +- src/include/nodes/execnodes.h | 9 +- 6 files changed, 85 insertions(+), 210 deletions(-) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 38cffde583..61d9a1d3cf 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -3110,32 +3110,15 @@ CopyFrom(CopyState cstate) } /* - * If we're capturing transition tuples, we might need to convert - * from the partition rowtype to root rowtype. + * If we're capturing transition tuples and there are no BEFORE + * triggers on the partition, we can just use the original + * unconverted tuple instead of converting the tuple in partition + * format back to root format. We must do the conversion if such + * triggers exist because they may change the tuple. */ 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 diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index faeea16d21..191f1418fe 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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" @@ -4652,9 +4653,7 @@ 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. * * 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 @@ -5748,13 +5747,23 @@ 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; + TupleTableSlot *original_insert_tuple; + PartitionRoutingInfo *pinfo = relinfo->ri_PartitionInfo; + TupleConversionMap *map = pinfo ? + pinfo->pi_PartitionToRootMap : + 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; bool insert_new_table = transition_capture->tcs_insert_new_table; + /* + * Get the originally inserted tuple from TransitionCaptureState and + * set the variable to NULL so that the same tuple is not read again. + */ + original_insert_tuple = transition_capture->tcs_original_insert_tuple; + transition_capture->tcs_original_insert_tuple = NULL; + /* * For INSERT events NEW should be non-NULL, for DELETE events OLD * should be non-NULL, whereas for UPDATE events normally both OLD and diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index d23f292cb0..d7ed3fcdbe 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -931,9 +931,22 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, 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)); + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + + /* + * If the partition appears to be an UPDATE result relation, the map + * has already been initialized by ExecInitModifyTable(); use that one + * instead of building one from scratch. To distinguish UPDATE result + * relations from tuple-routing result relations, we rely on the fact + * that each of the former has a distinct RT index. + */ + if (node && node->rootRelation != partRelInfo->ri_RangeTableIndex) + partrouteinfo->pi_PartitionToRootMap = + partRelInfo->ri_ChildToRootMap; + else + partrouteinfo->pi_PartitionToRootMap = + convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc), + RelationGetDescr(partRelInfo->ri_PartitionRoot)); } else partrouteinfo->pi_PartitionToRootMap = NULL; diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 1362b2f2d1..9de5a5371c 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -73,9 +73,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate, TupleTableSlot *slot, ResultRelInfo **partRelInfo); static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node); -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 @@ -339,10 +336,6 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, * relations. * * Returns RETURNING result if any, otherwise NULL. - * - * This may change the currently active tuple conversion map in - * mtstate->mt_transition_capture, so the callers must take care to - * save the previous value to avoid losing track of it. * ---------------------------------------------------------------- */ static TupleTableSlot * @@ -1051,9 +1044,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; + TupleConversionMap *tupconv_map = resultRelInfo->ri_ChildToRootMap; bool tuple_deleted; TupleTableSlot *epqslot = NULL; @@ -1129,41 +1120,16 @@ 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[]. - */ - map_index = resultRelInfo - mtstate->resultRelInfo; - Assert(map_index >= 0 && map_index < mtstate->mt_nplans); - tupconv_map = tupconv_map_for_subplan(mtstate, map_index); 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. */ Assert(mtstate->rootResultRelInfo != NULL); *inserted_tuple = ExecInsert(mtstate, mtstate->rootResultRelInfo, slot, planSlot, estate, canSetTag); - /* Clear the INSERT's tuple and restore the saved map. */ - 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; } @@ -1868,28 +1834,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); - } } /* @@ -1913,6 +1857,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, ResultRelInfo *partrel; PartitionRoutingInfo *partrouteinfo; TupleConversionMap *map; + bool has_before_insert_row_trig; /* * Look up the target partition's ResultRelInfo. If ExecFindPartition @@ -1927,37 +1872,17 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, Assert(partrouteinfo != NULL); /* - * If we're capturing transition tuples, we might need to convert from the - * partition rowtype to root partitioned table's rowtype. + * If we're capturing transition tuples and there are no BEFORE + * triggers on the partition, we can just use the original + * unconverted tuple instead of converting the tuple in partition + * format back to root format. We must do the conversion if such + * triggers exist because they may change the tuple. */ + has_before_insert_row_trig = (partrel->ri_TrigDesc && + partrel->ri_TrigDesc->trig_insert_before_row); 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; - } + mtstate->mt_transition_capture->tcs_original_insert_tuple = + !has_before_insert_row_trig ? slot : NULL; /* * Convert the tuple, if necessary. @@ -1973,58 +1898,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 = getTargetResultRelInfo(mtstate); - 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 * @@ -2120,17 +1993,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 @@ -2299,6 +2161,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) int i; Relation rel; bool update_tuple_routing_needed = node->partColsUpdated; + ResultRelInfo *rootResultRel; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -2321,8 +2184,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* If modifying a partitioned table, initialize the root table info */ if (node->rootResultRelIndex >= 0) + { mtstate->rootResultRelInfo = estate->es_root_result_relations + node->rootResultRelIndex; + rootResultRel = mtstate->rootResultRelInfo; + } + else + rootResultRel = mtstate->resultRelInfo; mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans); mtstate->mt_nplans = nplans; @@ -2331,6 +2199,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 @@ -2397,6 +2272,20 @@ 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. + */ + 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(rootResultRel->ri_RelationDesc)); resultRelInfo++; i++; } @@ -2421,26 +2310,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. diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index a46feeedb0..bb080980c0 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -45,7 +45,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. */ @@ -64,14 +64,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 diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 62e20fdfdc..62eed595ca 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -485,6 +485,12 @@ typedef struct ResultRelInfo /* For use by copy.c when performing multi-inserts */ struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer; + + /* + * Map to convert child sublan tuples to root parent format, set only if + * either update row movement or transition tuple capture is active. + */ + TupleConversionMap *ri_ChildToRootMap; } ResultRelInfo; /* ---------------- @@ -1185,9 +1191,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; /* ---------------- -- 2.16.5