(2018/03/09 20:18), Etsuro Fujita wrote:
Here are updated patches for PG10 and HEAD.
Other changes:
* Add regression tests based on your test cases shown upthread
I added a little bit more regression tests and revised comments. Please
find attached an updated patch.
Best regards,
Etsuro Fujita
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2656,2668 **** CopyFrom(CopyState cstate)
if (cstate->transition_capture != NULL)
{
if (resultRelInfo->ri_TrigDesc &&
! (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
! resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
{
/*
! * If there are any BEFORE or INSTEAD 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 =
--- 2656,2667 ----
if (cstate->transition_capture != NULL)
{
if (resultRelInfo->ri_TrigDesc &&
! resultRelInfo->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.
*/
cstate->transition_capture->tcs_original_insert_tuple = NULL;
cstate->transition_capture->tcs_map =
***************
*** 2803,2814 **** CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
! if (saved_resultRelInfo)
! {
! resultRelInfo = saved_resultRelInfo;
! estate->es_result_relation_info = resultRelInfo;
! }
}
}
--- 2802,2814 ----
* tuples inserted by an INSERT command.
*/
processed++;
+ }
! /* Restore the saved ResultRelInfo */
! if (saved_resultRelInfo)
! {
! resultRelInfo = saved_resultRelInfo;
! estate->es_result_relation_info = resultRelInfo;
}
}
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 62,67 **** static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
--- 62,71 ----
EState *estate,
bool canSetTag,
TupleTableSlot **returning);
+ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ EState *estate,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
***************
*** 259,265 **** ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
- ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
--- 263,268 ----
***************
*** 275,374 **** ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
-
- /* Determine the partition to heap_insert the tuple into */
- if (mtstate->mt_partition_dispatch_info)
- {
- int leaf_part_index;
- TupleConversionMap *map;
-
- /*
- * Away we go ... If we end up not finding a partition after all,
- * ExecFindPartition() does not return and errors out instead.
- * Otherwise, the returned value is to be used as an index into arrays
- * mt_partitions[] and mt_partition_tupconv_maps[] that will get us
- * the ResultRelInfo and TupleConversionMap for the partition,
- * respectively.
- */
- leaf_part_index = ExecFindPartition(resultRelInfo,
- mtstate->mt_partition_dispatch_info,
- slot,
- estate);
- Assert(leaf_part_index >= 0 &&
- leaf_part_index < mtstate->mt_num_partitions);
-
- /*
- * Save the old ResultRelInfo and switch to the one corresponding to
- * the selected partition.
- */
- saved_resultRelInfo = resultRelInfo;
- resultRelInfo = mtstate->mt_partitions + leaf_part_index;
-
- /* We do not yet have a way to insert into a foreign partition */
- if (resultRelInfo->ri_FdwRoutine)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot route inserted tuples to a foreign table")));
-
- /* For ExecInsertIndexTuples() to work on the partition's indexes */
- estate->es_result_relation_info = resultRelInfo;
-
- /*
- * If we're capturing transition tuples, we might need to convert from
- * the partition rowtype to parent rowtype.
- */
- if (mtstate->mt_transition_capture != NULL)
- {
- if (resultRelInfo->ri_TrigDesc &&
- (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
- resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
- {
- /*
- * If there are any BEFORE or INSTEAD 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 =
- mtstate->mt_transition_tupconv_maps[leaf_part_index];
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- mtstate->mt_oc_transition_capture->tcs_map =
- mtstate->mt_transition_tupconv_maps[leaf_part_index];
-
- /*
- * We might need to convert from the parent rowtype to the partition
- * rowtype.
- */
- map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
- if (map)
- {
- Relation partrel = resultRelInfo->ri_RelationDesc;
-
- tuple = do_convert_tuple(tuple, map);
-
- /*
- * We must use the partition's tuple descriptor from this point
- * on, until we're finished dealing with the partition. Use the
- * dedicated slot for that.
- */
- slot = mtstate->mt_partition_tuple_slot;
- Assert(slot != NULL);
- ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
- ExecStoreTuple(tuple, slot, InvalidBuffer, true);
- }
- }
-
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
--- 278,283 ----
***************
*** 477,483 **** ExecInsert(ModifyTableState *mtstate,
* No need though if the tuple has been routed, and a BR trigger
* doesn't exist.
*/
! if (saved_resultRelInfo != NULL &&
!(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
--- 386,392 ----
* No need though if the tuple has been routed, and a BR trigger
* doesn't exist.
*/
! if (resultRelInfo->ri_PartitionRoot != NULL &&
!(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
***************
*** 645,653 **** ExecInsert(ModifyTableState *mtstate,
if (resultRelInfo->ri_projectReturning)
result = ExecProcessReturning(resultRelInfo, slot, planSlot);
- if (saved_resultRelInfo)
- estate->es_result_relation_info = saved_resultRelInfo;
-
return result;
}
--- 554,559 ----
***************
*** 1545,1550 **** ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
--- 1451,1563 ----
}
}
+ /*
+ * ExecPrepareTupleRouting --- prepare for tuple routing of one tuple
+ *
+ * Returns a slot holding the routed tuple of the partition rowtype
+ */
+ static TupleTableSlot *
+ ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ EState *estate,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot)
+ {
+ int leaf_part_index;
+ ResultRelInfo *resultRelInfo;
+ HeapTuple tuple;
+ TupleConversionMap *map;
+
+ /*
+ * Away we go ... If we end up not finding a partition after all,
+ * ExecFindPartition() does not return and errors out instead.
+ * Otherwise, the returned value is to be used as an index into arrays
+ * mt_partitions[] and mt_partition_tupconv_maps[] that will get us
+ * the ResultRelInfo and TupleConversionMap for the partition,
+ * respectively.
+ */
+ leaf_part_index = ExecFindPartition(targetRelInfo,
+ mtstate->mt_partition_dispatch_info,
+ slot,
+ estate);
+ Assert(leaf_part_index >= 0 &&
+ leaf_part_index < mtstate->mt_num_partitions);
+
+ /* Get the ResultRelInfo corresponding to the selected partition. */
+ resultRelInfo = &mtstate->mt_partitions[leaf_part_index];
+
+ /* We do not yet have a way to insert into a foreign partition */
+ if (resultRelInfo->ri_FdwRoutine)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot route inserted tuples to a foreign table")));
+
+ /*
+ * For ExecInsert(), make it look like we are inserting into the partition.
+ */
+ estate->es_result_relation_info = resultRelInfo;
+
+ /* Get the heap tuple out of the given slot. */
+ tuple = ExecMaterializeSlot(slot);
+
+ /*
+ * If we're capturing transition tuples, we might need to convert from the
+ * partition rowtype to parent rowtype.
+ */
+ if (mtstate->mt_transition_capture != NULL)
+ {
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->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 =
+ mtstate->mt_transition_tupconv_maps[leaf_part_index];
+ }
+ else
+ {
+ /*
+ * Otherwise, just remember the original unconverted tuple, to
+ * avoid a needless round trip conversion.
+ */
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple;
+ mtstate->mt_transition_capture->tcs_map = NULL;
+ }
+ }
+ if (mtstate->mt_oc_transition_capture != NULL)
+ {
+ mtstate->mt_oc_transition_capture->tcs_map =
+ mtstate->mt_transition_tupconv_maps[leaf_part_index];
+ }
+
+ /*
+ * We might need to convert from the parent rowtype to the partition
+ * rowtype.
+ */
+ map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
+ if (map)
+ {
+ Relation partrel = resultRelInfo->ri_RelationDesc;
+
+ tuple = do_convert_tuple(tuple, map);
+
+ /*
+ * We must use the partition's tuple descriptor from this point on,
+ * until we're finished dealing with the partition. Use the
+ * dedicated slot for that.
+ */
+ slot = mtstate->mt_partition_tuple_slot;
+ Assert(slot != NULL);
+ ExecSetSlotDescriptor(slot, RelationGetDescr(partrel));
+ ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+ }
+
+ return slot;
+ }
+
+
/* ----------------------------------------------------------------
* ExecModifyTable
*
***************
*** 1763,1771 **** ExecModifyTable(PlanState *pstate)
--- 1776,1794 ----
switch (operation)
{
case CMD_INSERT:
+ /* Prepare for tuple routing of the tuple if needed. */
+ if (node->mt_partition_dispatch_info)
+ slot = ExecPrepareTupleRouting(node, estate,
+ resultRelInfo, slot);
slot = ExecInsert(node, slot, planSlot,
node->mt_arbiterindexes, node->mt_onconflict,
estate, node->canSetTag);
+ /*
+ * Revert back the active result relation that
+ * ExecPrepareTupleRouting would have changed.
+ */
+ if (node->mt_partition_dispatch_info)
+ estate->es_result_relation_info = resultRelInfo;
break;
case CMD_UPDATE:
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
*** a/src/test/regress/expected/insert.out
--- b/src/test/regress/expected/insert.out
***************
*** 554,559 **** drop role regress_coldesc_role;
--- 554,578 ----
drop table inserttest3;
drop table brtrigpartcon;
drop function brtrigpartcon1trigf();
+ -- check that "do nothing" BR triggers work with tuple-routing
+ -- (this ensures that estate->es_result_relation_info is set properly
+ -- for every routed tuple)
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create trigger donothingbrtrig before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
+ alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1);
+ create table donothingbrtrig_test2 partition of donothingbrtrig_test for values in (2);
+ insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar');
+ select tableoid::regclass, * from donothingbrtrig_test;
+ tableoid | a | b
+ -----------------------+---+-----
+ donothingbrtrig_test2 | 2 | bar
+ (1 row)
+
+ -- cleanup
+ drop table donothingbrtrig_test;
+ drop function donothingbrtrig_func();
-- check multi-column range partitioning with minvalue/maxvalue constraints
create table mcrparted (a text, b int) partition by range(a, b);
create table mcrparted1_lt_b partition of mcrparted for values from (minvalue, minvalue) to ('b', minvalue);
*** a/src/test/regress/sql/insert.sql
--- b/src/test/regress/sql/insert.sql
***************
*** 378,383 **** drop table inserttest3;
--- 378,402 ----
drop table brtrigpartcon;
drop function brtrigpartcon1trigf();
+ -- check that "do nothing" BR triggers work with tuple-routing
+ -- (this ensures that estate->es_result_relation_info is set properly
+ -- for every routed tuple)
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create trigger donothingbrtrig before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
+ alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1);
+ create table donothingbrtrig_test2 partition of donothingbrtrig_test for values in (2);
+
+ insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar');
+
+ select tableoid::regclass, * from donothingbrtrig_test;
+
+ -- cleanup
+ drop table donothingbrtrig_test;
+ drop function donothingbrtrig_func();
+
-- check multi-column range partitioning with minvalue/maxvalue constraints
create table mcrparted (a text, b int) partition by range(a, b);
create table mcrparted1_lt_b partition of mcrparted for values from (minvalue, minvalue) to ('b', minvalue);
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2633,2645 **** CopyFrom(CopyState cstate)
if (cstate->transition_capture != NULL)
{
if (resultRelInfo->ri_TrigDesc &&
! (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
! resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
{
/*
! * If there are any BEFORE or INSTEAD 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 =
--- 2633,2644 ----
if (cstate->transition_capture != NULL)
{
if (resultRelInfo->ri_TrigDesc &&
! resultRelInfo->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.
*/
cstate->transition_capture->tcs_original_insert_tuple = NULL;
cstate->transition_capture->tcs_map =
***************
*** 2768,2779 **** CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
! if (saved_resultRelInfo)
! {
! resultRelInfo = saved_resultRelInfo;
! estate->es_result_relation_info = resultRelInfo;
! }
}
}
--- 2767,2779 ----
* tuples inserted by an INSERT command.
*/
processed++;
+ }
! /* Restore the saved ResultRelInfo */
! if (saved_resultRelInfo)
! {
! resultRelInfo = saved_resultRelInfo;
! estate->es_result_relation_info = resultRelInfo;
}
}
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 67,72 **** static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
--- 67,77 ----
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
int whichplan);
+ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ PartitionTupleRouting *proute,
+ EState *estate,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
***************
*** 265,271 **** ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
- ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
--- 270,275 ----
***************
*** 282,381 **** ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
-
- /* Determine the partition to heap_insert the tuple into */
- if (mtstate->mt_partition_tuple_routing)
- {
- int leaf_part_index;
- PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
-
- /*
- * Away we go ... If we end up not finding a partition after all,
- * ExecFindPartition() does not return and errors out instead.
- * Otherwise, the returned value is to be used as an index into arrays
- * proute->partitions[] and proute->partition_tupconv_maps[] that will
- * get us the ResultRelInfo and TupleConversionMap for the partition,
- * respectively.
- */
- leaf_part_index = ExecFindPartition(resultRelInfo,
- proute->partition_dispatch_info,
- slot,
- estate);
- Assert(leaf_part_index >= 0 &&
- leaf_part_index < proute->num_partitions);
-
- /*
- * Save the old ResultRelInfo and switch to the one corresponding to
- * the selected partition. (We might need to initialize it first.)
- */
- saved_resultRelInfo = resultRelInfo;
- resultRelInfo = proute->partitions[leaf_part_index];
- if (resultRelInfo == NULL)
- {
- resultRelInfo = ExecInitPartitionInfo(mtstate,
- saved_resultRelInfo,
- proute, estate,
- leaf_part_index);
- Assert(resultRelInfo != NULL);
- }
-
- /* We do not yet have a way to insert into a foreign partition */
- if (resultRelInfo->ri_FdwRoutine)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot route inserted tuples to a foreign table")));
-
- /* For ExecInsertIndexTuples() to work on the partition's indexes */
- estate->es_result_relation_info = resultRelInfo;
-
- /*
- * If we're capturing transition tuples, we might need to convert from
- * the partition rowtype to parent rowtype.
- */
- if (mtstate->mt_transition_capture != NULL)
- {
- if (resultRelInfo->ri_TrigDesc &&
- (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
- resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
- {
- /*
- * If there are any BEFORE or INSTEAD 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 =
- TupConvMapForLeaf(proute, saved_resultRelInfo,
- leaf_part_index);
- }
- else
- {
- /*
- * Otherwise, just remember the original unconverted tuple, to
- * avoid a needless round trip conversion.
- */
- mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple;
- mtstate->mt_transition_capture->tcs_map = NULL;
- }
- }
- if (mtstate->mt_oc_transition_capture != NULL)
- {
- mtstate->mt_oc_transition_capture->tcs_map =
- TupConvMapForLeaf(proute, saved_resultRelInfo,
- leaf_part_index);
- }
-
- /*
- * We might need to convert from the parent rowtype to the partition
- * rowtype.
- */
- tuple = ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps[leaf_part_index],
- tuple,
- proute->partition_tuple_slot,
- &slot);
- }
-
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
--- 286,291 ----
***************
*** 495,501 **** ExecInsert(ModifyTableState *mtstate,
* No need though if the tuple has been routed, and a BR trigger
* doesn't exist.
*/
! if (saved_resultRelInfo != NULL &&
!(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
--- 405,411 ----
* No need though if the tuple has been routed, and a BR trigger
* doesn't exist.
*/
! if (resultRelInfo->ri_PartitionRoot != NULL &&
!(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
***************
*** 686,694 **** ExecInsert(ModifyTableState *mtstate,
if (resultRelInfo->ri_projectReturning)
result = ExecProcessReturning(resultRelInfo, slot, planSlot);
- if (saved_resultRelInfo)
- estate->es_result_relation_info = saved_resultRelInfo;
-
return result;
}
--- 596,601 ----
***************
*** 1209,1230 **** lreplace:;
proute->root_tuple_slot,
&slot);
!
! /*
! * For ExecInsert(), make it look like we are inserting into the
! * root.
! */
Assert(mtstate->rootResultRelInfo != NULL);
! estate->es_result_relation_info = mtstate->rootResultRelInfo;
ret_slot = ExecInsert(mtstate, slot, planSlot, NULL,
ONCONFLICT_NONE, estate, canSetTag);
/*
! * Revert back the active result relation and the active
! * transition capture map that we changed above.
*/
estate->es_result_relation_info = resultRelInfo;
if (mtstate->mt_transition_capture)
{
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
--- 1116,1139 ----
proute->root_tuple_slot,
&slot);
! /* Prepare for tuple routing of the tuple */
Assert(mtstate->rootResultRelInfo != NULL);
! slot = ExecPrepareTupleRouting(mtstate, proute, estate,
! mtstate->rootResultRelInfo, slot);
ret_slot = ExecInsert(mtstate, slot, planSlot, NULL,
ONCONFLICT_NONE, estate, canSetTag);
/*
! * Revert back the active result relation that
! * ExecPrepareTupleRouting would have changed.
*/
estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * Also, revert back the active transition capture map that we
+ * changed above.
+ */
if (mtstate->mt_transition_capture)
{
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
***************
*** 1835,1840 **** tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
--- 1744,1851 ----
}
}
+ /*
+ * ExecPrepareTupleRouting --- prepare for tuple routing of one tuple
+ *
+ * Returns a slot holding the routed tuple of the partition rowtype
+ */
+ static TupleTableSlot *
+ ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ PartitionTupleRouting *proute,
+ EState *estate,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot)
+ {
+ int leaf_part_index;
+ ResultRelInfo *resultRelInfo;
+ HeapTuple tuple;
+
+ Assert(proute != NULL);
+
+ /*
+ * Away we go ... If we end up not finding a partition after all,
+ * ExecFindPartition() does not return and errors out instead.
+ * Otherwise, the returned value is to be used as an index into arrays
+ * proute->partitions[] and proute->partition_tupconv_maps[] that will
+ * get us the ResultRelInfo and TupleConversionMap for the partition,
+ * respectively.
+ */
+ leaf_part_index = ExecFindPartition(targetRelInfo,
+ proute->partition_dispatch_info,
+ slot,
+ estate);
+ Assert(leaf_part_index >= 0 && leaf_part_index < proute->num_partitions);
+
+ /* Get the ResultRelInfo corresponding to the selected partition. */
+ resultRelInfo = proute->partitions[leaf_part_index];
+ if (resultRelInfo == NULL)
+ {
+ resultRelInfo = ExecInitPartitionInfo(mtstate, targetRelInfo,
+ proute, estate,
+ leaf_part_index);
+ Assert(resultRelInfo != NULL);
+ }
+
+ /* We do not yet have a way to insert into a foreign partition */
+ if (resultRelInfo->ri_FdwRoutine)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot route inserted tuples to a foreign table")));
+
+ /*
+ * For ExecInsert(), make it look like we are inserting into the partition.
+ */
+ estate->es_result_relation_info = resultRelInfo;
+
+ /* Get the heap tuple out of the given slot. */
+ tuple = ExecMaterializeSlot(slot);
+
+ /*
+ * If we're capturing transition tuples, we might need to convert from the
+ * partition rowtype to parent rowtype.
+ */
+ if (mtstate->mt_transition_capture != NULL)
+ {
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->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 =
+ TupConvMapForLeaf(proute, targetRelInfo, leaf_part_index);
+ }
+ else
+ {
+ /*
+ * Otherwise, just remember the original unconverted tuple, to
+ * avoid a needless round trip conversion.
+ */
+ mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple;
+ mtstate->mt_transition_capture->tcs_map = NULL;
+ }
+ }
+ if (mtstate->mt_oc_transition_capture != NULL)
+ {
+ mtstate->mt_oc_transition_capture->tcs_map =
+ TupConvMapForLeaf(proute, targetRelInfo, leaf_part_index);
+ }
+
+ /*
+ * We might need to convert from the parent rowtype to the partition
+ * rowtype.
+ */
+ ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps[leaf_part_index],
+ tuple,
+ proute->partition_tuple_slot,
+ &slot);
+
+ return slot;
+ }
+
+
/* ----------------------------------------------------------------
* ExecModifyTable
*
***************
*** 1846,1851 **** static TupleTableSlot *
--- 1857,1863 ----
ExecModifyTable(PlanState *pstate)
{
ModifyTableState *node = castNode(ModifyTableState, pstate);
+ PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
EState *estate = node->ps.state;
CmdType operation = node->operation;
ResultRelInfo *saved_resultRelInfo;
***************
*** 2051,2059 **** ExecModifyTable(PlanState *pstate)
--- 2063,2081 ----
switch (operation)
{
case CMD_INSERT:
+ /* Prepare for tuple routing of the tuple if needed. */
+ if (proute)
+ slot = ExecPrepareTupleRouting(node, proute, estate,
+ resultRelInfo, slot);
slot = ExecInsert(node, slot, planSlot,
node->mt_arbiterindexes, node->mt_onconflict,
estate, node->canSetTag);
+ /*
+ * Revert back the active result relation that
+ * ExecPrepareTupleRouting would have changed.
+ */
+ if (proute)
+ estate->es_result_relation_info = resultRelInfo;
break;
case CMD_UPDATE:
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
*** a/src/test/regress/expected/insert.out
--- b/src/test/regress/expected/insert.out
***************
*** 748,753 **** drop role regress_coldesc_role;
--- 748,772 ----
drop table inserttest3;
drop table brtrigpartcon;
drop function brtrigpartcon1trigf();
+ -- check that "do nothing" BR triggers work with tuple-routing
+ -- (this ensures that estate->es_result_relation_info is set properly
+ -- for every routed tuple)
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create trigger donothingbrtrig before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
+ alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1);
+ create table donothingbrtrig_test2 partition of donothingbrtrig_test for values in (2);
+ insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar');
+ select tableoid::regclass, * from donothingbrtrig_test;
+ tableoid | a | b
+ -----------------------+---+-----
+ donothingbrtrig_test2 | 2 | bar
+ (1 row)
+
+ -- cleanup
+ drop table donothingbrtrig_test;
+ drop function donothingbrtrig_func();
-- check multi-column range partitioning with minvalue/maxvalue constraints
create table mcrparted (a text, b int) partition by range(a, b);
create table mcrparted1_lt_b partition of mcrparted for values from (minvalue, minvalue) to ('b', minvalue);
*** a/src/test/regress/sql/insert.sql
--- b/src/test/regress/sql/insert.sql
***************
*** 489,494 **** drop table inserttest3;
--- 489,513 ----
drop table brtrigpartcon;
drop function brtrigpartcon1trigf();
+ -- check that "do nothing" BR triggers work with tuple-routing
+ -- (this ensures that estate->es_result_relation_info is set properly
+ -- for every routed tuple)
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create trigger donothingbrtrig before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
+ alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1);
+ create table donothingbrtrig_test2 partition of donothingbrtrig_test for values in (2);
+
+ insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar');
+
+ select tableoid::regclass, * from donothingbrtrig_test;
+
+ -- cleanup
+ drop table donothingbrtrig_test;
+ drop function donothingbrtrig_func();
+
-- check multi-column range partitioning with minvalue/maxvalue constraints
create table mcrparted (a text, b int) partition by range(a, b);
create table mcrparted1_lt_b partition of mcrparted for values from (minvalue, minvalue) to ('b', minvalue);