Hi hackers,
I noticed that runtime stats for BEFORE ROW INSERT triggers on leaf
partitions of partitioned tables aren't reported in EXPLAIN ANALYZE.
Here is an example:
postgres=# create table trigger_test (a int, b text) partition by list (a);
CREATE TABLE
postgres=# create table trigger_test1 partition of trigger_test for
values in (1);
CREATE TABLE
postgres=# create trigger before_ins_row_trig BEFORE INSERT ON
trigger_test1 FOR EACH ROW EXECUTE PROCEDURE trigger_data();
CREATE TRIGGER
postgres=# create trigger after_ins_row_trig AFTER INSERT ON
trigger_test1 FOR EACH ROW EXECUTE PROCEDURE trigger_data();
CREATE TRIGGER
postgres=# explain analyze insert into trigger_test values (1, 'foo');
NOTICE: before_ins_row_trig() BEFORE ROW INSERT ON trigger_test1
NOTICE: NEW: (1,foo)
NOTICE: after_ins_row_trig() AFTER ROW INSERT ON trigger_test1
NOTICE: NEW: (1,foo)
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Insert on trigger_test (cost=0.00..0.01 rows=1 width=36) (actual
time=0.193..0.193 rows=0 loops=1)
-> Result (cost=0.00..0.01 rows=1 width=36) (actual
time=0.002..0.003 rows=1 loops=1)
Planning time: 0.027 ms
Trigger after_ins_row_trig on trigger_test1: time=0.075 calls=1
Execution time: 0.310 ms
(5 rows)
where trig_data() is borrowed from the regression test in postgres_fdw.
The stats for the AFTER ROW INSERT trigger after_ins_row_trig are well
shown in the output, but the stats for the BEFORE ROW INSERT trigger
before_ins_row_trig aren't at all. I think we should show the latter as
well.
Another thing I noticed is: runtime stats for BEFORE STATEMENT
UPDATE/DELETE triggers on partitioned table roots aren't reported in
EXPLAIN ANALYZE, either, as shown in a below example:
postgres=# create trigger before_upd_stmt_trig BEFORE UPDATE ON
trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER
postgres=# create trigger after_upd_stmt_trig AFTER UPDATE ON
trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER
postgres=# explain analyze update trigger_test set b = 'bar' where a = 1;
NOTICE: trigger_func(<NULL>) called: action = UPDATE, when = BEFORE,
level = STATEMENT
NOTICE: trigger_func(<NULL>) called: action = UPDATE, when = AFTER,
level = STATEMENT
QUERY PLAN
----------------------------------------------------------------------------------------------------------
-----
Update on trigger_test (cost=0.00..25.88 rows=6 width=42) (actual
time=0.296..0.296 rows=0 loops=1)
Update on trigger_test1
-> Seq Scan on trigger_test1 (cost=0.00..25.88 rows=6 width=42)
(actual time=0.010..0.011 rows=1 loops=1)
Filter: (a = 1)
Planning time: 0.152 ms
Trigger after_upd_stmt_trig on trigger_test: time=0.141 calls=1
Execution time: 0.476 ms
(7 rows)
where trigger_func() is borrowed from the regression test, too. The
stats for the BEFORE STATEMENT UPDATE trigger before_upd_stmt_trig
aren't shown at all in the output. I think this should also be fixed.
So here is a patch for fixing both issues. Changes I made are:
* To fix the former, I added a new List member es_leaf_result_relations
to EState, modified ExecSetupPartitionTupleRouting so that it creates
ResultRelInfos with the EState's es_instrument and then saves them in
that list, and modified ExplainPrintTriggers to show stats for BEFORE
ROW INSERT triggers on leaf partitions (if any) by looking at that list.
I also modified copy.c so that ExecSetupPartitionTupleRouting and
related things are performed in CopyFrom after its EState creation.
* To fix the latter, I modified ExplainPrintTriggers to show stats for
BEFORE STATEMENT UPDATE/DELETE triggers on partitioned table roots (if
any) by looking at the es_root_result_relations array.
* While fixing these, I noticed that AFTER ROW INSERT triggers on leaf
partitions and BEFORE STATEMENT UPDATE/DELETE triggers on partitioned
table roots re-open relations and re-create ResultRelInfos (trigger-only
ResultRelInfos!) in ExecGetTriggerResultRel when executing triggers (and
that in the above examples, the stats for AFTER ROW INSERT trigger/AFTER
STATEMENT UPDATE trigger are shown the result for
es_trig_target_relations in ExplainPrintTriggers). But that wouldn't be
efficient (and EXPLAIN ANALYZE might produce odd outputs), so I modified
ExecGetTriggerResultRel so that it searches es_leaf_result_relations and
es_root_result_relations in addition to es_result_relations.
Best regards,
Etsuro Fujita
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 1415,1473 **** BeginCopy(ParseState *pstate,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have
OIDs",
RelationGetRelationName(cstate->rel))));
-
- /*
- * If there are any triggers with transition tables on the named
- * relation, we need to be prepared to capture transition
tuples.
- */
- cstate->transition_capture =
MakeTransitionCaptureState(rel->trigdesc);
-
- /* Initialize state for CopyFrom tuple routing. */
- if (is_from && rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE)
- {
- PartitionDispatch *partition_dispatch_info;
- ResultRelInfo *partitions;
- TupleConversionMap **partition_tupconv_maps;
- TupleTableSlot *partition_tuple_slot;
- int num_parted,
- num_partitions;
-
- ExecSetupPartitionTupleRouting(rel,
-
1,
-
&partition_dispatch_info,
-
&partitions,
-
&partition_tupconv_maps,
-
&partition_tuple_slot,
-
&num_parted, &num_partitions);
- cstate->partition_dispatch_info =
partition_dispatch_info;
- cstate->num_dispatch = num_parted;
- cstate->partitions = partitions;
- cstate->num_partitions = num_partitions;
- cstate->partition_tupconv_maps = partition_tupconv_maps;
- cstate->partition_tuple_slot = partition_tuple_slot;
-
- /*
- * If we are capturing transition tuples, they may need
to be
- * converted from partition format back to partitioned
table
- * format (this is only ever necessary if a BEFORE
trigger
- * modifies the tuple).
- */
- if (cstate->transition_capture != NULL)
- {
- int i;
-
- cstate->transition_tupconv_maps =
(TupleConversionMap **)
- palloc0(sizeof(TupleConversionMap *) *
- cstate->num_partitions);
- for (i = 0; i < cstate->num_partitions; ++i)
- {
- cstate->transition_tupconv_maps[i] =
-
convert_tuples_by_name(RelationGetDescr(cstate->partitions[i].ri_RelationDesc),
-
RelationGetDescr(rel),
-
gettext_noop("could not convert row type"));
- }
- }
- }
}
else
{
--- 1415,1420 ----
***************
*** 2483,2488 **** CopyFrom(CopyState cstate)
--- 2430,2492 ----
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
/*
+ * If there are any triggers with transition tables on the named
relation,
+ * we need to be prepared to capture transition tuples.
+ */
+ cstate->transition_capture =
+ MakeTransitionCaptureState(cstate->rel->trigdesc);
+
+ /*
+ * If the named relation is a partitioned table, initialize state for
+ * CopyFrom tuple routing.
+ */
+ if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ PartitionDispatch *partition_dispatch_info;
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ TupleTableSlot *partition_tuple_slot;
+ int num_parted,
+ num_partitions;
+
+ ExecSetupPartitionTupleRouting(cstate->rel,
+ 1,
+
estate,
+
&partition_dispatch_info,
+
&partitions,
+
&partition_tupconv_maps,
+
&partition_tuple_slot,
+
&num_parted, &num_partitions);
+ cstate->partition_dispatch_info = partition_dispatch_info;
+ cstate->num_dispatch = num_parted;
+ cstate->partitions = partitions;
+ cstate->num_partitions = num_partitions;
+ cstate->partition_tupconv_maps = partition_tupconv_maps;
+ cstate->partition_tuple_slot = partition_tuple_slot;
+
+ /*
+ * If we are capturing transition tuples, they may need to be
+ * converted from partition format back to partitioned table
format
+ * (this is only ever necessary if a BEFORE trigger modifies the
+ * tuple).
+ */
+ if (cstate->transition_capture != NULL)
+ {
+ int i;
+
+ cstate->transition_tupconv_maps = (TupleConversionMap
**)
+ palloc0(sizeof(TupleConversionMap *) *
cstate->num_partitions);
+ for (i = 0; i < cstate->num_partitions; ++i)
+ {
+ cstate->transition_tupconv_maps[i] =
+
convert_tuples_by_name(RelationGetDescr(cstate->partitions[i].ri_RelationDesc),
+
RelationGetDescr(cstate->rel),
+
gettext_noop("could not convert row type"));
+ }
+ }
+ }
+
+ /*
* It's more efficient to prepare a bunch of tuples for insertion, and
* insert them in one heap_multi_insert() call, than call heap_insert()
* separately for every tuple. However, we can't do that if there are
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 656,672 **** ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
ResultRelInfo *rInfo;
bool show_relname;
int numrels =
queryDesc->estate->es_num_result_relations;
List *targrels = queryDesc->estate->es_trig_target_relations;
int nr;
ListCell *l;
ExplainOpenGroup("Triggers", "Triggers", false, es);
! show_relname = (numrels > 1 || targrels != NIL);
rInfo = queryDesc->estate->es_result_relations;
for (nr = 0; nr < numrels; rInfo++, nr++)
report_triggers(rInfo, show_relname, es);
foreach(l, targrels)
{
rInfo = (ResultRelInfo *) lfirst(l);
--- 656,685 ----
ResultRelInfo *rInfo;
bool show_relname;
int numrels =
queryDesc->estate->es_num_result_relations;
+ int numrootrels =
queryDesc->estate->es_num_root_result_relations;
+ List *leafrels = queryDesc->estate->es_leaf_result_relations;
List *targrels = queryDesc->estate->es_trig_target_relations;
int nr;
ListCell *l;
ExplainOpenGroup("Triggers", "Triggers", false, es);
! show_relname = (numrels > 1 || numrootrels > 0 ||
! leafrels != NIL || targrels != NIL);
rInfo = queryDesc->estate->es_result_relations;
for (nr = 0; nr < numrels; rInfo++, nr++)
report_triggers(rInfo, show_relname, es);
+ rInfo = queryDesc->estate->es_root_result_relations;
+ for (nr = 0; nr < numrootrels; rInfo++, nr++)
+ report_triggers(rInfo, show_relname, es);
+
+ foreach(l, leafrels)
+ {
+ rInfo = (ResultRelInfo *) lfirst(l);
+ report_triggers(rInfo, show_relname, es);
+ }
+
foreach(l, targrels)
{
rInfo = (ResultRelInfo *) lfirst(l);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1364,1379 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
*
* Get a ResultRelInfo for a trigger target relation. Most of the time,
* triggers are fired on one of the result relations of the query, and so
! * we can just return a member of the es_result_relations array. (Note: in
! * self-join situations there might be multiple members with the same OID;
! * if so it doesn't matter which one we pick.) However, it is sometimes
! * necessary to fire triggers on other relations; this happens mainly when an
! * RI update trigger queues additional triggers on other relations, which will
! * be processed in the context of the outer query. For efficiency's sake,
! * we want to have a ResultRelInfo for those triggers too; that can avoid
! * repeated re-opening of the relation. (It also provides a way for EXPLAIN
! * ANALYZE to report the runtimes of such triggers.) So we make additional
! * ResultRelInfo's as needed, and save them in es_trig_target_relations.
*/
ResultRelInfo *
ExecGetTriggerResultRel(EState *estate, Oid relid)
--- 1364,1381 ----
*
* Get a ResultRelInfo for a trigger target relation. Most of the time,
* triggers are fired on one of the result relations of the query, and so
! * we can just return a member of the es_result_relations array, the
! * es_root_result_relations array (if any), or the es_leaf_result_relations
! * list (if any). (Note: in self-join situations there might be multiple
! * members with the same OID; if so it doesn't matter which one we pick.)
! * However, it is sometimes necessary to fire triggers on other relations;
! * this happens mainly when an RI update trigger queues additional triggers
! * on other relations, which will be processed in the context of the outer
! * query. For efficiency's sake, we want to have a ResultRelInfo for those
! * triggers too; that can avoid repeated re-opening of the relation. (It
! * also provides a way for EXPLAIN ANALYZE to report the runtimes of such
! * triggers.) So we make additional ResultRelInfo's as needed, and save them
! * in es_trig_target_relations.
*/
ResultRelInfo *
ExecGetTriggerResultRel(EState *estate, Oid relid)
***************
*** 1394,1399 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
--- 1396,1418 ----
rInfo++;
nr--;
}
+ /* Second, search through the root result relations, if any */
+ rInfo = estate->es_root_result_relations;
+ nr = estate->es_num_root_result_relations;
+ while (nr > 0)
+ {
+ if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ return rInfo;
+ rInfo++;
+ nr--;
+ }
+ /* Third, search through the leaf result relations, if any */
+ foreach(l, estate->es_leaf_result_relations)
+ {
+ rInfo = (ResultRelInfo *) lfirst(l);
+ if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ return rInfo;
+ }
/* Nope, but maybe we already made an extra ResultRelInfo for it */
foreach(l, estate->es_trig_target_relations)
{
***************
*** 3237,3242 **** EvalPlanQualEnd(EPQState *epqstate)
--- 3256,3262 ----
void
ExecSetupPartitionTupleRouting(Relation rel,
Index resultRTindex,
+ EState *estate,
PartitionDispatch
**pd,
ResultRelInfo
**partitions,
TupleConversionMap
***tup_conv_maps,
***************
*** 3297,3303 **** ExecSetupPartitionTupleRouting(Relation rel,
partrel,
resultRTindex,
rel,
! 0);
/*
* Open partition indices (remember we do not support ON
CONFLICT in
--- 3317,3326 ----
partrel,
resultRTindex,
rel,
! estate->es_instrument);
!
! estate->es_leaf_result_relations =
! lappend(estate->es_leaf_result_relations,
leaf_part_rri);
/*
* Open partition indices (remember we do not support ON
CONFLICT in
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 115,120 **** CreateExecutorState(void)
--- 115,125 ----
estate->es_num_result_relations = 0;
estate->es_result_relation_info = NULL;
+ estate->es_root_result_relations = NULL;
+ estate->es_num_root_result_relations = 0;
+
+ estate->es_leaf_result_relations = NIL;
+
estate->es_trig_target_relations = NIL;
estate->es_trig_tuple_slot = NULL;
estate->es_trig_oldtup_slot = NULL;
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 1919,1924 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int
eflags)
--- 1919,1925 ----
ExecSetupPartitionTupleRouting(rel,
node->nominalRelation,
+
estate,
&partition_dispatch_info,
&partitions,
&partition_tupconv_maps,
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 208,213 **** extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
--- 208,214 ----
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
extern void ExecSetupPartitionTupleRouting(Relation rel,
Index resultRTindex,
+ EState *estate,
PartitionDispatch
**pd,
ResultRelInfo
**partitions,
TupleConversionMap
***tup_conv_maps,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 452,457 **** typedef struct EState
--- 452,460 ----
ResultRelInfo *es_root_result_relations; /* array of
ResultRelInfos */
int es_num_root_result_relations; /* length of
the array */
+ /* Info about leaf partitions of partitioned table(s) for insert
queries: */
+ List *es_leaf_result_relations; /* List of ResultRelInfos */
+
/* Stuff used for firing triggers: */
List *es_trig_target_relations; /* trigger-only ResultRelInfos
*/
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers