Fix interaction of triggers, partitioning, and EXPLAIN ANALYZE.
Add a new EState member es_leaf_result_relations, so that the trigger code knows about ResultRelInfos created by tuple routing. Also make sure ExplainPrintTriggers knows about partition-related ResultRelInfos. Etsuro Fujita, reviewed by Amit Langote Discussion: http://postgr.es/m/57163e18-8e56-da83-337a-22f2c0008051@lab.ntt.co.jp
This commit is contained in:
parent
a20aac890a
commit
c4b841ba6a
|
@ -1415,59 +1415,6 @@ BeginCopy(ParseState *pstate,
|
||||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
errmsg("table \"%s\" does not have OIDs",
|
errmsg("table \"%s\" does not have OIDs",
|
||||||
RelationGetRelationName(cstate->rel))));
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -2482,6 +2429,63 @@ CopyFrom(CopyState cstate)
|
||||||
/* Triggers might need a slot as well */
|
/* Triggers might need a slot as well */
|
||||||
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
|
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
|
* 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()
|
* insert them in one heap_multi_insert() call, than call heap_insert()
|
||||||
|
|
|
@ -656,17 +656,30 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
|
||||||
ResultRelInfo *rInfo;
|
ResultRelInfo *rInfo;
|
||||||
bool show_relname;
|
bool show_relname;
|
||||||
int numrels = queryDesc->estate->es_num_result_relations;
|
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;
|
List *targrels = queryDesc->estate->es_trig_target_relations;
|
||||||
int nr;
|
int nr;
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
|
|
||||||
ExplainOpenGroup("Triggers", "Triggers", false, es);
|
ExplainOpenGroup("Triggers", "Triggers", false, es);
|
||||||
|
|
||||||
show_relname = (numrels > 1 || targrels != NIL);
|
show_relname = (numrels > 1 || numrootrels > 0 ||
|
||||||
|
leafrels != NIL || targrels != NIL);
|
||||||
rInfo = queryDesc->estate->es_result_relations;
|
rInfo = queryDesc->estate->es_result_relations;
|
||||||
for (nr = 0; nr < numrels; rInfo++, nr++)
|
for (nr = 0; nr < numrels; rInfo++, nr++)
|
||||||
report_triggers(rInfo, show_relname, es);
|
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)
|
foreach(l, targrels)
|
||||||
{
|
{
|
||||||
rInfo = (ResultRelInfo *) lfirst(l);
|
rInfo = (ResultRelInfo *) lfirst(l);
|
||||||
|
|
|
@ -1365,16 +1365,18 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
||||||
*
|
*
|
||||||
* Get a ResultRelInfo for a trigger target relation. Most of the time,
|
* 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
|
* 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
|
* we can just return a member of the es_result_relations array, the
|
||||||
* self-join situations there might be multiple members with the same OID;
|
* es_root_result_relations array (if any), or the es_leaf_result_relations
|
||||||
* if so it doesn't matter which one we pick.) However, it is sometimes
|
* list (if any). (Note: in self-join situations there might be multiple
|
||||||
* necessary to fire triggers on other relations; this happens mainly when an
|
* members with the same OID; if so it doesn't matter which one we pick.)
|
||||||
* RI update trigger queues additional triggers on other relations, which will
|
* However, it is sometimes necessary to fire triggers on other relations;
|
||||||
* be processed in the context of the outer query. For efficiency's sake,
|
* this happens mainly when an RI update trigger queues additional triggers
|
||||||
* we want to have a ResultRelInfo for those triggers too; that can avoid
|
* on other relations, which will be processed in the context of the outer
|
||||||
* repeated re-opening of the relation. (It also provides a way for EXPLAIN
|
* query. For efficiency's sake, we want to have a ResultRelInfo for those
|
||||||
* ANALYZE to report the runtimes of such triggers.) So we make additional
|
* triggers too; that can avoid repeated re-opening of the relation. (It
|
||||||
* ResultRelInfo's as needed, and save them in es_trig_target_relations.
|
* 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 *
|
ResultRelInfo *
|
||||||
ExecGetTriggerResultRel(EState *estate, Oid relid)
|
ExecGetTriggerResultRel(EState *estate, Oid relid)
|
||||||
|
@ -1395,6 +1397,23 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
|
||||||
rInfo++;
|
rInfo++;
|
||||||
nr--;
|
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 */
|
/* Nope, but maybe we already made an extra ResultRelInfo for it */
|
||||||
foreach(l, estate->es_trig_target_relations)
|
foreach(l, estate->es_trig_target_relations)
|
||||||
{
|
{
|
||||||
|
@ -3238,6 +3257,7 @@ EvalPlanQualEnd(EPQState *epqstate)
|
||||||
void
|
void
|
||||||
ExecSetupPartitionTupleRouting(Relation rel,
|
ExecSetupPartitionTupleRouting(Relation rel,
|
||||||
Index resultRTindex,
|
Index resultRTindex,
|
||||||
|
EState *estate,
|
||||||
PartitionDispatch **pd,
|
PartitionDispatch **pd,
|
||||||
ResultRelInfo **partitions,
|
ResultRelInfo **partitions,
|
||||||
TupleConversionMap ***tup_conv_maps,
|
TupleConversionMap ***tup_conv_maps,
|
||||||
|
@ -3301,7 +3321,10 @@ ExecSetupPartitionTupleRouting(Relation rel,
|
||||||
partrel,
|
partrel,
|
||||||
resultRTindex,
|
resultRTindex,
|
||||||
rel,
|
rel,
|
||||||
0);
|
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
|
* Open partition indices (remember we do not support ON CONFLICT in
|
||||||
|
|
|
@ -115,6 +115,11 @@ CreateExecutorState(void)
|
||||||
estate->es_num_result_relations = 0;
|
estate->es_num_result_relations = 0;
|
||||||
estate->es_result_relation_info = NULL;
|
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_target_relations = NIL;
|
||||||
estate->es_trig_tuple_slot = NULL;
|
estate->es_trig_tuple_slot = NULL;
|
||||||
estate->es_trig_oldtup_slot = NULL;
|
estate->es_trig_oldtup_slot = NULL;
|
||||||
|
|
|
@ -1919,6 +1919,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
||||||
|
|
||||||
ExecSetupPartitionTupleRouting(rel,
|
ExecSetupPartitionTupleRouting(rel,
|
||||||
node->nominalRelation,
|
node->nominalRelation,
|
||||||
|
estate,
|
||||||
&partition_dispatch_info,
|
&partition_dispatch_info,
|
||||||
&partitions,
|
&partitions,
|
||||||
&partition_tupconv_maps,
|
&partition_tupconv_maps,
|
||||||
|
|
|
@ -208,6 +208,7 @@ extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
|
||||||
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
|
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
|
||||||
extern void ExecSetupPartitionTupleRouting(Relation rel,
|
extern void ExecSetupPartitionTupleRouting(Relation rel,
|
||||||
Index resultRTindex,
|
Index resultRTindex,
|
||||||
|
EState *estate,
|
||||||
PartitionDispatch **pd,
|
PartitionDispatch **pd,
|
||||||
ResultRelInfo **partitions,
|
ResultRelInfo **partitions,
|
||||||
TupleConversionMap ***tup_conv_maps,
|
TupleConversionMap ***tup_conv_maps,
|
||||||
|
|
|
@ -452,6 +452,9 @@ typedef struct EState
|
||||||
ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
|
ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */
|
||||||
int es_num_root_result_relations; /* length of the array */
|
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: */
|
/* Stuff used for firing triggers: */
|
||||||
List *es_trig_target_relations; /* trigger-only ResultRelInfos */
|
List *es_trig_target_relations; /* trigger-only ResultRelInfos */
|
||||||
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
|
TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
|
||||||
|
|
Loading…
Reference in New Issue