(2018/03/19 17:48), Amit Langote wrote:
On 2018/03/16 20:37, Etsuro Fujita wrote:
(2018/03/14 17:25), Etsuro Fujita wrote:
(2018/03/14 14:54), Amit Langote wrote:
Btw, I noticed that the patches place ExecPrepareTupleRouting (both the
declaration and the definition) at different relative locations within
nodeModifyTable.c in the HEAD branch vs. in the 10 branch. It might be a
good idea to bring them to the same relative location to avoid hassle
when
back-patching relevant patches in the future. I did that in the attached
updated version (v4) of the patch for HEAD, which does not make any other
changes. Although, the patch for PG-10 is unchanged, I still changed its
file name to contain v4.

That's a good idea! Thanks for the updated patches!

Sorry, I didn't look at those patches carefully, but I noticed that while
the patch for PG10 has put the definition of that function after the
definitions of functions such as fireBSTriggers, fireASTriggers and
ExecSetupTransitionCaptureState, but the patch for HEAD puts it *before*
the definitions of these functions (note: these functions don't have their
declarations at the file header), which seems a bit inconsistent.  ISTM
that the v3 patches for PG10 and HEAD have already put that function in
the right place in terms of that relativity.

That's correct, except v3 had added the definition of
ExecPrepareTupleRouting after those of ExecSetupChildParentMapForSubplan,
ExecSetupChildParentMapForTcs, etc., that are new in 11dev branch.

I've fixed the patch for HEAD to move the ExecPrepareTupleRouting
definition to appear after those of fireBSTriggers, fireASTriggers, and
ExecSetupTransitionCaptureState.

Attached are v5 patches for HEAD and 10 branches.

Thanks for the updated patches! I think the patches are in good shape, but I did a bit of editorial things; added a bit more comments for ExecPrepareTupleRouting and adjusted regression test stuff to match the existing ones. Attached are the updated patches for HEAD and PG10. Those changes are just editorial, so let's ask for the committer review.

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,1566 ----
  	}
  }
  
+ /*
+  * ExecPrepareTupleRouting --- prepare for tuple routing of one tuple
+  *
+  * Determine which partition to insert the tuple into and prepare for
+  * ExecInsert() to insert the tuple into the selected partition.
+  *
+  * 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)
--- 1779,1797 ----
  		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 checks
+ -- that estate->es_result_relation_info is appropriately set/reset for each
+ -- routed tuple)
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+ 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,399 ----
  drop table brtrigpartcon;
  drop function brtrigpartcon1trigf();
  
+ -- check that "do nothing" BR triggers work with tuple-routing (this checks
+ -- that estate->es_result_relation_info is appropriately set/reset for each
+ -- routed tuple)
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+ 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
***************
*** 62,67 **** static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
--- 62,72 ----
  					 EState *estate,
  					 bool canSetTag,
  					 TupleTableSlot **returning);
+ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ 						PartitionTupleRouting *proute,
+ 						EState *estate,
+ 						ResultRelInfo *targetRelInfo,
+ 						TupleTableSlot *slot);
  static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
  static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
  static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
***************
*** 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;
***************
*** 1711,1716 **** ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
--- 1620,1729 ----
  }
  
  /*
+  * ExecPrepareTupleRouting --- prepare for tuple routing of one tuple
+  *
+  * Determine which partition to insert the tuple into and prepare for
+  * ExecInsert() to insert the tuple into the selected partition.
+  *
+  * 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;
+ }
+ 
+ /*
   * 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
***************
*** 1835,1840 **** tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
--- 1848,1854 ----
  	}
  }
  
+ 
  /* ----------------------------------------------------------------
   *	   ExecModifyTable
   *
***************
*** 1846,1851 **** static TupleTableSlot *
--- 1860,1866 ----
  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)
--- 2066,2084 ----
  		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 checks
+ -- that estate->es_result_relation_info is appropriately set/reset for each
+ -- routed tuple)
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+ 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,510 ----
  drop table brtrigpartcon;
  drop function brtrigpartcon1trigf();
  
+ -- check that "do nothing" BR triggers work with tuple-routing (this checks
+ -- that estate->es_result_relation_info is appropriately set/reset for each
+ -- routed tuple)
+ create table donothingbrtrig_test (a int, b text) partition by list (a);
+ create table donothingbrtrig_test1 (b text, a int);
+ create or replace function donothingbrtrig_func() returns trigger as $$begin return NULL; end$$ language plpgsql;
+ 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);

Reply via email to