aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/copy.c20
-rw-r--r--src/backend/executor/nodeModifyTable.c230
-rw-r--r--src/include/nodes/execnodes.h12
-rw-r--r--src/test/regress/expected/insert.out26
-rw-r--r--src/test/regress/sql/insert.sql23
5 files changed, 188 insertions, 123 deletions
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 4562a5121d4..a42861da0df 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2633,13 +2633,12 @@ 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))
+ resultRelInfo->ri_TrigDesc->trig_insert_before_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.
+ * 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,12 +2767,13 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+ }
- if (saved_resultRelInfo)
- {
- resultRelInfo = saved_resultRelInfo;
- estate->es_result_relation_info = resultRelInfo;
- }
+ /* Restore the saved ResultRelInfo */
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
}
}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 3332ae4bf3d..9260236e4d9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -62,6 +62,11 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
EState *estate,
bool canSetTag,
TupleTableSlot **returning);
+static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot);
static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
@@ -265,7 +270,6 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
- ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -282,100 +286,6 @@ 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;
/*
@@ -495,7 +405,7 @@ ExecInsert(ModifyTableState *mtstate,
* No need though if the tuple has been routed, and a BR trigger
* doesn't exist.
*/
- if (saved_resultRelInfo != NULL &&
+ if (resultRelInfo->ri_PartitionRoot != NULL &&
!(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_before_row))
check_partition_constr = false;
@@ -686,9 +596,6 @@ ExecInsert(ModifyTableState *mtstate,
if (resultRelInfo->ri_projectReturning)
result = ExecProcessReturning(resultRelInfo, slot, planSlot);
- if (saved_resultRelInfo)
- estate->es_result_relation_info = saved_resultRelInfo;
-
return result;
}
@@ -1209,27 +1116,22 @@ lreplace:;
proute->root_tuple_slot,
&slot);
-
- /*
- * For ExecInsert(), make it look like we are inserting into the
- * root.
- */
+ /* Prepare for tuple routing */
Assert(mtstate->rootResultRelInfo != NULL);
- estate->es_result_relation_info = mtstate->rootResultRelInfo;
+ slot = ExecPrepareTupleRouting(mtstate, estate, proute,
+ mtstate->rootResultRelInfo, slot);
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.
- */
+ /* Revert ExecPrepareTupleRouting's node change. */
estate->es_result_relation_info = resultRelInfo;
if (mtstate->mt_transition_capture)
{
mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
mtstate->mt_transition_capture->tcs_map = saved_tcs_map;
}
+
return ret_slot;
}
@@ -1711,6 +1613,108 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
}
/*
+ * ExecPrepareTupleRouting --- prepare for routing one tuple
+ *
+ * Determine the partition in which the tuple in slot is to be inserted,
+ * and modify mtstate and estate to prepare for it.
+ *
+ * Caller must revert the estate changes after executing the insertion!
+ * In mtstate, transition capture changes may also need to be reverted.
+ *
+ * Returns a slot holding the tuple of the partition rowtype.
+ */
+static TupleTableSlot *
+ExecPrepareTupleRouting(ModifyTableState *mtstate,
+ EState *estate,
+ PartitionTupleRouting *proute,
+ ResultRelInfo *targetRelInfo,
+ TupleTableSlot *slot)
+{
+ int partidx;
+ ResultRelInfo *partrel;
+ HeapTuple tuple;
+
+ /*
+ * Determine the target partition. If ExecFindPartition does not find
+ * a partition after all, it doesn't return here; otherwise, the returned
+ * value is to be used as an index into the arrays for the ResultRelInfo
+ * and TupleConversionMap for the partition.
+ */
+ partidx = ExecFindPartition(targetRelInfo,
+ proute->partition_dispatch_info,
+ slot,
+ estate);
+ Assert(partidx >= 0 && partidx < proute->num_partitions);
+
+ /*
+ * Get the ResultRelInfo corresponding to the selected partition; if not
+ * yet there, initialize it.
+ */
+ partrel = proute->partitions[partidx];
+ if (partrel == NULL)
+ partrel = ExecInitPartitionInfo(mtstate, targetRelInfo,
+ proute, estate,
+ partidx);
+
+ /* We do not yet have a way to insert into a foreign partition */
+ if (partrel->ri_FdwRoutine)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot route inserted tuples to a foreign table")));
+
+ /*
+ * Make it look like we are inserting into the partition.
+ */
+ estate->es_result_relation_info = partrel;
+
+ /* 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 (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 =
+ TupConvMapForLeaf(proute, targetRelInfo, partidx);
+ }
+ 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, partidx);
+ }
+
+ /*
+ * Convert the tuple, if necessary.
+ */
+ ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps[partidx],
+ 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
@@ -1846,6 +1850,7 @@ static TupleTableSlot *
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,9 +2056,16 @@ ExecModifyTable(PlanState *pstate)
switch (operation)
{
case CMD_INSERT:
+ /* Prepare for tuple routing if needed. */
+ if (proute)
+ slot = ExecPrepareTupleRouting(node, estate, proute,
+ resultRelInfo, slot);
slot = ExecInsert(node, slot, planSlot,
node->mt_arbiterindexes, node->mt_onconflict,
estate, node->canSetTag);
+ /* Revert ExecPrepareTupleRouting's state change. */
+ if (proute)
+ estate->es_result_relation_info = resultRelInfo;
break;
case CMD_UPDATE:
slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a953820f43a..b119ef861b9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -995,14 +995,18 @@ typedef struct ModifyTableState
TupleTableSlot *mt_existing; /* slot to store existing target tuple in */
List *mt_excludedtlist; /* the excluded pseudo relation's tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection target */
- struct PartitionTupleRouting *mt_partition_tuple_routing;
+
/* Tuple-routing support info */
- struct TransitionCaptureState *mt_transition_capture;
+ struct PartitionTupleRouting *mt_partition_tuple_routing;
+
/* controls transition table population for specified operation */
- struct TransitionCaptureState *mt_oc_transition_capture;
+ struct TransitionCaptureState *mt_transition_capture;
+
/* controls transition table population for INSERT...ON CONFLICT UPDATE */
- TupleConversionMap **mt_per_subplan_tupconv_maps;
+ struct TransitionCaptureState *mt_oc_transition_capture;
+
/* Per plan map for tuple conversion from child to root */
+ TupleConversionMap **mt_per_subplan_tupconv_maps;
} ModifyTableState;
/* ----------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 2ca085e1ba3..97419a744ff 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -748,6 +748,32 @@ drop role regress_coldesc_role;
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 table donothingbrtrig_test2 (c text, b text, a int);
+alter table donothingbrtrig_test2 drop column c;
+create or replace function donothingbrtrig_func() returns trigger as $$begin raise notice 'b: %', new.b; return NULL; end$$ language plpgsql;
+create trigger donothingbrtrig1 before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
+create trigger donothingbrtrig2 before insert on donothingbrtrig_test2 for each row execute procedure donothingbrtrig_func();
+alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1);
+alter table donothingbrtrig_test attach partition donothingbrtrig_test2 for values in (2);
+insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar');
+NOTICE: b: foo
+NOTICE: b: bar
+copy donothingbrtrig_test from stdout;
+NOTICE: b: baz
+NOTICE: b: qux
+select tableoid::regclass, * from donothingbrtrig_test;
+ tableoid | a | b
+----------+---+---
+(0 rows)
+
+-- 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);
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 3e3fd96c757..a16f2a7f890 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -489,6 +489,29 @@ 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 table donothingbrtrig_test2 (c text, b text, a int);
+alter table donothingbrtrig_test2 drop column c;
+create or replace function donothingbrtrig_func() returns trigger as $$begin raise notice 'b: %', new.b; return NULL; end$$ language plpgsql;
+create trigger donothingbrtrig1 before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
+create trigger donothingbrtrig2 before insert on donothingbrtrig_test2 for each row execute procedure donothingbrtrig_func();
+alter table donothingbrtrig_test attach partition donothingbrtrig_test1 for values in (1);
+alter table donothingbrtrig_test attach partition donothingbrtrig_test2 for values in (2);
+insert into donothingbrtrig_test values (1, 'foo'), (2, 'bar');
+copy donothingbrtrig_test from stdout;
+1 baz
+2 qux
+\.
+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);