Improve EXPLAIN ANALYZE to show the time spent in each trigger when

executing a statement that fires triggers.  Formerly this time was
included in "Total runtime" but not otherwise accounted for.
As a side benefit, we avoid re-opening relations when firing non-deferred
AFTER triggers, because the trigger code can re-use the main executor's
ResultRelInfo data structure.
This commit is contained in:
Tom Lane 2005-03-25 21:58:00 +00:00
parent 08890b407e
commit adb1a6e95b
16 changed files with 403 additions and 143 deletions

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/perform.sgml,v 1.50 2005/02/03 07:12:37 neilc Exp $
$PostgreSQL: pgsql/doc/src/sgml/perform.sgml,v 1.51 2005/03/25 21:57:57 tgl Exp $
-->
<chapter id="performance-tips">
@ -320,16 +320,19 @@ EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 &lt; 50 AND t1
</para>
<para>
The <literal>Total runtime</literal> shown by <command>EXPLAIN ANALYZE</command> includes
executor start-up and shut-down time, as well as time spent processing
the result rows. It does not include parsing, rewriting, or planning
time. For a <command>SELECT</> query, the total run time will normally be just a
little larger than the total time reported for the top-level plan node.
For <command>INSERT</>, <command>UPDATE</>, and <command>DELETE</> commands, the total run time may be
considerably larger, because it includes the time spent processing the
result rows. In these commands, the time for the top plan node
essentially is the time spent computing the new rows and/or locating
the old ones, but it doesn't include the time spent making the changes.
The <literal>Total runtime</literal> shown by <command>EXPLAIN
ANALYZE</command> includes executor start-up and shut-down time, as well
as time spent processing the result rows. It does not include parsing,
rewriting, or planning time. For a <command>SELECT</> query, the total
run time will normally be just a little larger than the total time
reported for the top-level plan node. For <command>INSERT</>,
<command>UPDATE</>, and <command>DELETE</> commands, the total run time
may be considerably larger, because it includes the time spent processing
the result rows. In these commands, the time for the top plan node
essentially is the time spent computing the new rows and/or locating the
old ones, but it doesn't include the time spent making the changes.
Time spent firing triggers, if any, is also outside the top plan node,
and is shown separately for each trigger.
</para>
<para>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.22 2004/12/31 21:59:38 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.23 2005/03/25 21:57:57 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,12 +21,14 @@
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@ -510,3 +512,95 @@ RemoveConstraintById(Oid conId)
systable_endscan(conscan);
heap_close(conDesc, RowExclusiveLock);
}
/*
* GetConstraintNameForTrigger
* Get the name of the constraint owning a trigger, if any
*
* Returns a palloc'd string, or NULL if no constraint can be found
*/
char *
GetConstraintNameForTrigger(Oid triggerId)
{
char *result;
Oid constraintId = InvalidOid;
Oid pg_trigger_id;
Oid pg_constraint_id;
Relation depRel;
Relation conRel;
ScanKeyData key[2];
SysScanDesc scan;
HeapTuple tup;
pg_trigger_id = get_system_catalog_relid(TriggerRelationName);
pg_constraint_id = get_system_catalog_relid(ConstraintRelationName);
/*
* We must grovel through pg_depend to find the owning constraint.
* Perhaps pg_trigger should have a column for the owning constraint ...
* but right now this is not performance-critical code.
*/
depRel = heap_openr(DependRelationName, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_depend_classid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(pg_trigger_id));
ScanKeyInit(&key[1],
Anum_pg_depend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(triggerId));
/* assume we can ignore objsubid for a trigger */
scan = systable_beginscan(depRel, DependDependerIndex, true,
SnapshotNow, 2, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
if (foundDep->refclassid == pg_constraint_id &&
foundDep->deptype == DEPENDENCY_INTERNAL)
{
constraintId = foundDep->refobjid;
break;
}
}
systable_endscan(scan);
heap_close(depRel, AccessShareLock);
if (!OidIsValid(constraintId))
return NULL; /* no owning constraint found */
conRel = heap_openr(ConstraintRelationName, AccessShareLock);
ScanKeyInit(&key[0],
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(constraintId));
scan = systable_beginscan(conRel, ConstraintOidIndex, true,
SnapshotNow, 1, key);
tup = systable_getnext(scan);
if (HeapTupleIsValid(tup))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
result = pstrdup(NameStr(con->conname));
}
else
{
/* This arguably should be an error, but we'll just return NULL */
result = NULL;
}
systable_endscan(scan);
heap_close(conRel, AccessShareLock);
return result;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.238 2005/03/16 21:38:05 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.239 2005/03/25 21:57:57 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1510,6 +1510,10 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
resultRelInfo->ri_RangeTableIndex = 1; /* dummy */
resultRelInfo->ri_RelationDesc = rel;
resultRelInfo->ri_TrigDesc = CopyTriggerDesc(rel->trigdesc);
if (resultRelInfo->ri_TrigDesc)
resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
resultRelInfo->ri_TrigInstrument = NULL;
ExecOpenIndices(resultRelInfo);
@ -1974,7 +1978,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
/*
* Handle queued AFTER triggers
*/
AfterTriggerEndQuery();
AfterTriggerEndQuery(estate);
pfree(values);
pfree(nulls);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.130 2005/03/20 22:27:51 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.131 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -15,6 +15,7 @@
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/explain.h"
#include "commands/prepare.h"
@ -272,16 +273,76 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
}
/*
* Close down the query and free resources; also run any queued
* AFTER triggers. Include time for this in the total runtime.
* If we ran the command, run any AFTER triggers it queued. (Note this
* will not include DEFERRED triggers; since those don't run until end of
* transaction, we can't measure them.) Include into total runtime.
*/
if (stmt->analyze)
{
INSTR_TIME_SET_CURRENT(starttime);
AfterTriggerEndQuery(queryDesc->estate);
totaltime += elapsed_time(&starttime);
}
/* Print info about runtime of triggers */
if (es->printAnalyze)
{
ResultRelInfo *rInfo;
int numrels = queryDesc->estate->es_num_result_relations;
int nr;
rInfo = queryDesc->estate->es_result_relations;
for (nr = 0; nr < numrels; rInfo++, nr++)
{
int nt;
if (!rInfo->ri_TrigDesc)
continue;
for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
{
Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
char *conname;
/* Must clean up instrumentation state */
InstrEndLoop(instr);
/*
* We ignore triggers that were never invoked; they likely
* aren't relevant to the current query type.
*/
if (instr->ntuples == 0)
continue;
if (trig->tgisconstraint &&
(conname = GetConstraintNameForTrigger(trig->tgoid)) != NULL)
{
appendStringInfo(str, "Trigger for constraint %s",
conname);
pfree(conname);
}
else
appendStringInfo(str, "Trigger %s", trig->tgname);
if (numrels > 1)
appendStringInfo(str, " on %s",
RelationGetRelationName(rInfo->ri_RelationDesc));
appendStringInfo(str, ": time=%.3f calls=%.0f\n",
1000.0 * instr->total,
instr->ntuples);
}
}
}
/*
* Close down the query and free resources. Include time for this
* in the total runtime (although it should be pretty minimal).
*/
INSTR_TIME_SET_CURRENT(starttime);
ExecutorEnd(queryDesc);
if (stmt->analyze)
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc);
/* We need a CCI just in case query expanded to multiple plans */

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.38 2004/12/31 21:59:41 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.39 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -281,8 +281,8 @@ PortalCleanup(Portal portal)
PG_TRY();
{
CurrentResourceOwner = portal->resowner;
ExecutorEnd(queryDesc);
/* we do not need AfterTriggerEndQuery() here */
ExecutorEnd(queryDesc);
}
PG_CATCH();
{
@ -382,8 +382,8 @@ PersistHoldablePortal(Portal portal)
* Now shut down the inner executor.
*/
portal->queryDesc = NULL; /* prevent double shutdown */
ExecutorEnd(queryDesc);
/* we do not need AfterTriggerEndQuery() here */
ExecutorEnd(queryDesc);
/*
* Reset the position in the result set: ideally, this could be

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.180 2005/03/24 00:03:26 neilc Exp $
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.181 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -28,6 +28,7 @@
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/instrument.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
@ -46,7 +47,9 @@ static HeapTuple GetTupleForTrigger(EState *estate,
CommandId cid,
TupleTableSlot **newSlot);
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
int tgindx,
FmgrInfo *finfo,
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
@ -1107,20 +1110,26 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
* Call a trigger function.
*
* trigdata: trigger descriptor.
* finfo: possibly-cached call info for the function.
* tgindx: trigger's index in finfo and instr arrays.
* finfo: array of cached trigger function call information.
* instr: optional array of EXPLAIN ANALYZE instrumentation state.
* per_tuple_context: memory context to execute the function in.
*
* Returns the tuple (or NULL) as returned by the function.
*/
static HeapTuple
ExecCallTriggerFunc(TriggerData *trigdata,
int tgindx,
FmgrInfo *finfo,
Instrumentation *instr,
MemoryContext per_tuple_context)
{
FunctionCallInfoData fcinfo;
Datum result;
MemoryContext oldContext;
finfo += tgindx;
/*
* We cache fmgr lookup info, to avoid making the lookup again on each
* call.
@ -1130,6 +1139,12 @@ ExecCallTriggerFunc(TriggerData *trigdata,
Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);
/*
* If doing EXPLAIN ANALYZE, start charging time to this trigger.
*/
if (instr)
InstrStartNode(instr + tgindx);
/*
* Do the function evaluation in the per-tuple memory context, so that
* leaked memory will be reclaimed once per tuple. Note in particular
@ -1160,6 +1175,13 @@ ExecCallTriggerFunc(TriggerData *trigdata,
errmsg("trigger function %u returned null value",
fcinfo.flinfo->fn_oid)));
/*
* If doing EXPLAIN ANALYZE, stop charging time to this trigger,
* and count one "tuple returned" (really the number of firings).
*/
if (instr)
InstrStopNode(instr + tgindx, true);
return (HeapTuple) DatumGetPointer(result);
}
@ -1183,11 +1205,6 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_BEFORE;
@ -1205,7 +1222,9 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
tgindx[i],
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
@ -1237,11 +1256,6 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerData LocTriggerData;
int i;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_ROW |
@ -1259,7 +1273,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
tgindx[i],
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (oldtuple != newtuple && oldtuple != trigtuple)
heap_freetuple(oldtuple);
@ -1300,11 +1316,6 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_BEFORE;
@ -1322,7 +1333,9 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
tgindx[i],
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
@ -1360,11 +1373,6 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
if (trigtuple == NULL)
return false;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_ROW |
@ -1382,7 +1390,9 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
tgindx[i],
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple == NULL)
break;
@ -1433,11 +1443,6 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
@ -1455,7 +1460,9 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
tgindx[i],
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
@ -1501,11 +1508,6 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
if (newSlot != NULL)
intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_ROW |
@ -1523,7 +1525,9 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
tgindx[i],
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (oldtuple != newtuple && oldtuple != intuple)
heap_freetuple(oldtuple);
@ -1798,6 +1802,7 @@ static AfterTriggers afterTriggers;
static void AfterTriggerExecute(AfterTriggerEvent event,
Relation rel, TriggerDesc *trigdesc,
FmgrInfo *finfo,
Instrumentation *instr,
MemoryContext per_tuple_context);
static SetConstraintState SetConstraintStateCreate(int numalloc);
static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
@ -1886,18 +1891,22 @@ afterTriggerAddEvent(AfterTriggerEvent event)
*
* Frequently, this will be fired many times in a row for triggers of
* a single relation. Therefore, we cache the open relation and provide
* fmgr lookup cache space at the caller level.
* fmgr lookup cache space at the caller level. (For triggers fired at
* the end of a query, we can even piggyback on the executor's state.)
*
* event: event currently being fired.
* rel: open relation for event.
* trigdesc: working copy of rel's trigger info.
* finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
* instr: array of EXPLAIN ANALYZE instrumentation nodes (one per trigger),
* or NULL if no instrumentation is wanted.
* per_tuple_context: memory context to call trigger function in.
* ----------
*/
static void
AfterTriggerExecute(AfterTriggerEvent event,
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
Relation rel, TriggerDesc *trigdesc,
FmgrInfo *finfo, Instrumentation *instr,
MemoryContext per_tuple_context)
{
Oid tgoid = event->ate_tgoid;
@ -1909,6 +1918,28 @@ AfterTriggerExecute(AfterTriggerEvent event,
Buffer newbuffer = InvalidBuffer;
int tgindx;
/*
* Locate trigger in trigdesc.
*/
LocTriggerData.tg_trigger = NULL;
for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
{
if (trigdesc->triggers[tgindx].tgoid == tgoid)
{
LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
break;
}
}
if (LocTriggerData.tg_trigger == NULL)
elog(ERROR, "could not find trigger %u", tgoid);
/*
* If doing EXPLAIN ANALYZE, start charging time to this trigger.
* We want to include time spent re-fetching tuples in the trigger cost.
*/
if (instr)
InstrStartNode(instr + tgindx);
/*
* Fetch the required OLD and NEW tuples.
*/
@ -1934,18 +1965,6 @@ AfterTriggerExecute(AfterTriggerEvent event,
event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
LocTriggerData.tg_relation = rel;
LocTriggerData.tg_trigger = NULL;
for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
{
if (trigdesc->triggers[tgindx].tgoid == tgoid)
{
LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
break;
}
}
if (LocTriggerData.tg_trigger == NULL)
elog(ERROR, "could not find trigger %u", tgoid);
switch (event->ate_event & TRIGGER_EVENT_OPMASK)
{
case TRIGGER_EVENT_INSERT:
@ -1973,11 +1992,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
MemoryContextReset(per_tuple_context);
/*
* Call the trigger and throw away any eventually returned updated
* tuple.
* Call the trigger and throw away any possibly returned updated
* tuple. (Don't let ExecCallTriggerFunc measure EXPLAIN time.)
*/
rettuple = ExecCallTriggerFunc(&LocTriggerData,
finfo + tgindx,
tgindx,
finfo,
NULL,
per_tuple_context);
if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple)
heap_freetuple(rettuple);
@ -1989,6 +2010,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
ReleaseBuffer(oldbuffer);
if (newbuffer != InvalidBuffer)
ReleaseBuffer(newbuffer);
/*
* If doing EXPLAIN ANALYZE, stop charging time to this trigger,
* and count one "tuple returned" (really the number of firings).
*/
if (instr)
InstrStopNode(instr + tgindx, true);
}
@ -2093,6 +2121,12 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
* Scan the given event list for events that are marked as to be fired
* in the current firing cycle, and fire them.
*
* If estate isn't NULL, then we expect that all the firable events are
* for triggers of the relations included in the estate's result relation
* array. This allows us to re-use the estate's open relations and
* trigger cache info. When estate is NULL, we have to find the relations
* the hard way.
*
* When delete_ok is TRUE, it's okay to delete fully-processed events.
* The events list pointers are updated.
* ----------
@ -2100,6 +2134,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
static void
afterTriggerInvokeEvents(AfterTriggerEventList *events,
CommandId firing_id,
EState *estate,
bool delete_ok)
{
AfterTriggerEvent event,
@ -2108,6 +2143,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
Relation rel = NULL;
TriggerDesc *trigdesc = NULL;
FmgrInfo *finfo = NULL;
Instrumentation *instr = NULL;
/* Make a per-tuple memory context for trigger function calls */
per_tuple_context =
@ -2136,35 +2172,65 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
*/
if (rel == NULL || rel->rd_id != event->ate_relid)
{
if (rel)
heap_close(rel, NoLock);
if (trigdesc)
FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
if (estate)
{
/* Find target relation among estate's result rels */
ResultRelInfo *rInfo;
int nr;
/*
* We assume that an appropriate lock is still held by
* the executor, so grab no new lock here.
*/
rel = heap_open(event->ate_relid, NoLock);
rInfo = estate->es_result_relations;
nr = estate->es_num_result_relations;
while (nr > 0)
{
if (rInfo->ri_RelationDesc->rd_id == event->ate_relid)
break;
rInfo++;
nr--;
}
if (nr <= 0) /* should not happen */
elog(ERROR, "could not find relation %u among query result relations",
event->ate_relid);
rel = rInfo->ri_RelationDesc;
trigdesc = rInfo->ri_TrigDesc;
finfo = rInfo->ri_TrigFunctions;
instr = rInfo->ri_TrigInstrument;
}
else
{
/* Hard way: we manage the resources for ourselves */
if (rel)
heap_close(rel, NoLock);
if (trigdesc)
FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
Assert(instr == NULL); /* never used in this case */
/*
* Copy relation's trigger info so that we have a
* stable copy no matter what the called triggers do.
*/
trigdesc = CopyTriggerDesc(rel->trigdesc);
/*
* We assume that an appropriate lock is still held by
* the executor, so grab no new lock here.
*/
rel = heap_open(event->ate_relid, NoLock);
if (trigdesc == NULL) /* should not happen */
elog(ERROR, "relation %u has no triggers",
event->ate_relid);
/*
* Copy relation's trigger info so that we have a
* stable copy no matter what the called triggers do.
*/
trigdesc = CopyTriggerDesc(rel->trigdesc);
/*
* Allocate space to cache fmgr lookup info for
* triggers.
*/
finfo = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
if (trigdesc == NULL) /* should not happen */
elog(ERROR, "relation %u has no triggers",
event->ate_relid);
/*
* Allocate space to cache fmgr lookup info for
* triggers.
*/
finfo = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
/* Never any EXPLAIN info in this case */
}
}
/*
@ -2172,7 +2238,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
* set, so recursive examinations of the event list won't try
* to re-fire it.
*/
AfterTriggerExecute(event, rel, trigdesc, finfo,
AfterTriggerExecute(event, rel, trigdesc, finfo, instr,
per_tuple_context);
/*
@ -2214,12 +2280,16 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
events->tail = prev_event;
/* Release working resources */
if (rel)
heap_close(rel, NoLock);
if (trigdesc)
FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
if (!estate)
{
if (rel)
heap_close(rel, NoLock);
if (trigdesc)
FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
Assert(instr == NULL); /* never used in this case */
}
MemoryContextDelete(per_tuple_context);
}
@ -2308,10 +2378,14 @@ AfterTriggerBeginQuery(void)
* Called after one query has been completely processed. At this time
* we invoke all AFTER IMMEDIATE trigger events queued by the query, and
* transfer deferred trigger events to the global deferred-trigger list.
*
* Note that this should be called just BEFORE closing down the executor
* with ExecutorEnd, because we make use of the EState's info about
* target relations.
* ----------
*/
void
AfterTriggerEndQuery(void)
AfterTriggerEndQuery(EState *estate)
{
AfterTriggerEventList *events;
@ -2339,7 +2413,7 @@ AfterTriggerEndQuery(void)
CommandId firing_id = afterTriggers->firing_counter++;
/* OK to delete the immediate events after processing them */
afterTriggerInvokeEvents(events, firing_id, true);
afterTriggerInvokeEvents(events, firing_id, estate, true);
}
afterTriggers->query_depth--;
@ -2381,7 +2455,7 @@ AfterTriggerEndXact(void)
{
CommandId firing_id = afterTriggers->firing_counter++;
afterTriggerInvokeEvents(events, firing_id, true);
afterTriggerInvokeEvents(events, firing_id, NULL, true);
}
/*
@ -2838,7 +2912,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
* level, but we'd better not if inside a subtransaction, since
* the subtransaction could later get rolled back.
*/
afterTriggerInvokeEvents(events, firing_id,
afterTriggerInvokeEvents(events, firing_id, NULL,
!IsSubTransaction());
}
}

View File

@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.243 2005/03/20 23:40:25 neilc Exp $
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.244 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -39,6 +39,7 @@
#include "commands/trigger.h"
#include "executor/execdebug.h"
#include "executor/execdefs.h"
#include "executor/instrument.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
@ -69,7 +70,8 @@ static void InitPlan(QueryDesc *queryDesc, bool explainOnly);
static void initResultRelInfo(ResultRelInfo *resultRelInfo,
Index resultRelationIndex,
List *rangeTable,
CmdType operation);
CmdType operation,
bool doInstrument);
static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate,
CmdType operation,
long numberTuples,
@ -508,7 +510,8 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
initResultRelInfo(resultRelInfo,
lfirst_int(l),
rangeTable,
operation);
operation,
estate->es_instrument);
resultRelInfo++;
}
}
@ -523,7 +526,8 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
initResultRelInfo(resultRelInfos,
parseTree->resultRelation,
rangeTable,
operation);
operation,
estate->es_instrument);
}
estate->es_result_relations = resultRelInfos;
@ -798,7 +802,8 @@ static void
initResultRelInfo(ResultRelInfo *resultRelInfo,
Index resultRelationIndex,
List *rangeTable,
CmdType operation)
CmdType operation,
bool doInstrument)
{
Oid resultRelationOid;
Relation resultRelationDesc;
@ -837,7 +842,22 @@ initResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_IndexRelationInfo = NULL;
/* make a copy so as not to depend on relcache info not changing... */
resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc);
resultRelInfo->ri_TrigFunctions = NULL;
if (resultRelInfo->ri_TrigDesc)
{
int n = resultRelInfo->ri_TrigDesc->numtriggers;
resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(n * sizeof(FmgrInfo));
if (doInstrument)
resultRelInfo->ri_TrigInstrument = InstrAlloc(n);
else
resultRelInfo->ri_TrigInstrument = NULL;
}
else
{
resultRelInfo->ri_TrigFunctions = NULL;
resultRelInfo->ri_TrigInstrument = NULL;
}
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;

View File

@ -12,7 +12,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.46 2004/12/31 21:59:45 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.47 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -252,7 +252,7 @@ ExecInitNode(Plan *node, EState *estate)
/* Set up instrumentation for this node if requested */
if (estate->es_instrument)
result->instrument = InstrAlloc();
result->instrument = InstrAlloc(1);
return result;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.92 2005/03/16 21:38:07 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.93 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -399,8 +399,8 @@ postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
{
ActiveSnapshot = es->qd->snapshot;
AfterTriggerEndQuery(es->qd->estate);
ExecutorEnd(es->qd);
AfterTriggerEndQuery();
}
PG_CATCH();
{

View File

@ -7,7 +7,7 @@
* Copyright (c) 2001-2005, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/instrument.c,v 1.10 2005/03/20 22:27:51 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/instrument.c,v 1.11 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -18,13 +18,13 @@
#include "executor/instrument.h"
/* Allocate new instrumentation structure */
/* Allocate new instrumentation structure(s) */
Instrumentation *
InstrAlloc(void)
InstrAlloc(int n)
{
Instrumentation *instr = palloc(sizeof(Instrumentation));
Instrumentation *instr = palloc0(n * sizeof(Instrumentation));
memset(instr, 0, sizeof(Instrumentation));
/* we don't need to do any initialization except zero 'em */
return instr;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.135 2005/03/16 21:38:08 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.136 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1546,10 +1546,10 @@ _SPI_pquery(QueryDesc *queryDesc, int tcount)
elog(ERROR, "consistency check on SPI tuple count failed");
}
ExecutorEnd(queryDesc);
/* Take care of any queued AFTER triggers */
AfterTriggerEndQuery();
AfterTriggerEndQuery(queryDesc->estate);
ExecutorEnd(queryDesc);
if (queryDesc->dest->mydest == SPI)
{

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.92 2005/03/16 21:38:08 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.93 2005/03/25 21:57:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -206,14 +206,14 @@ ProcessQuery(Query *parsetree,
}
}
/* Now take care of any queued AFTER triggers */
AfterTriggerEndQuery(queryDesc->estate);
/*
* Now, we close down all the scans and free allocated resources.
*/
ExecutorEnd(queryDesc);
/* And take care of any queued AFTER triggers */
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc);
FreeSnapshot(ActiveSnapshot);

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.14 2004/12/31 22:03:24 pgsql Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.15 2005/03/25 21:57:59 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@ -181,4 +181,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
const char *label, Oid namespace,
List *others);
extern char *GetConstraintNameForTrigger(Oid triggerId);
#endif /* PG_CONSTRAINT_H */

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.51 2004/12/31 22:03:28 pgsql Exp $
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.52 2005/03/25 21:57:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -156,7 +156,7 @@ extern void ExecARUpdateTriggers(EState *estate,
extern void AfterTriggerBeginXact(void);
extern void AfterTriggerBeginQuery(void);
extern void AfterTriggerEndQuery(void);
extern void AfterTriggerEndQuery(EState *estate);
extern void AfterTriggerEndXact(void);
extern void AfterTriggerAbortXact(void);
extern void AfterTriggerBeginSubXact(void);

View File

@ -6,7 +6,7 @@
*
* Copyright (c) 2001-2005, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/executor/instrument.h,v 1.9 2005/03/20 22:27:52 tgl Exp $
* $PostgreSQL: pgsql/src/include/executor/instrument.h,v 1.10 2005/03/25 21:57:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -70,7 +70,7 @@ typedef struct Instrumentation
double nloops; /* # of run cycles for this node */
} Instrumentation;
extern Instrumentation *InstrAlloc(void);
extern Instrumentation *InstrAlloc(int n);
extern void InstrStartNode(Instrumentation *instr);
extern void InstrStopNode(Instrumentation *instr, bool returnedTuple);
extern void InstrEndLoop(Instrumentation *instr);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.124 2005/03/16 21:38:10 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.125 2005/03/25 21:58:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -261,6 +261,7 @@ typedef struct JunkFilter
* IndexRelationInfo array of key/attr info for indices
* TrigDesc triggers to be fired, if any
* TrigFunctions cached lookup info for trigger functions
* TrigInstrument optional runtime measurements for triggers
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* ----------------
@ -275,6 +276,7 @@ typedef struct ResultRelInfo
IndexInfo **ri_IndexRelationInfo;
TriggerDesc *ri_TrigDesc;
FmgrInfo *ri_TrigFunctions;
struct Instrumentation *ri_TrigInstrument;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
} ResultRelInfo;