<!--
-$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">
</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>
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
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;
+}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
/*
* Handle queued AFTER triggers
*/
- AfterTriggerEndQuery();
+ AfterTriggerEndQuery(estate);
pfree(values);
pfree(nulls);
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
}
/*
- * 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 */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
PG_TRY();
{
CurrentResourceOwner = portal->resowner;
- ExecutorEnd(queryDesc);
/* we do not need AfterTriggerEndQuery() here */
+ ExecutorEnd(queryDesc);
}
PG_CATCH();
{
* 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
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
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);
* 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.
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
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);
}
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;
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)
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 |
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);
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;
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)
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 |
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;
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;
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)
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 |
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);
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);
*
* 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;
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.
*/
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:
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);
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);
}
* 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.
* ----------
static void
afterTriggerInvokeEvents(AfterTriggerEventList *events,
CommandId firing_id,
+ EState *estate,
bool delete_ok)
{
AfterTriggerEvent event,
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 =
*/
if (rel == NULL || rel->rd_id != event->ate_relid)
{
- if (rel)
- heap_close(rel, NoLock);
- if (trigdesc)
- FreeTriggerDesc(trigdesc);
- if (finfo)
- pfree(finfo);
-
- /*
- * 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);
-
- /*
- * Copy relation's trigger info so that we have a
- * stable copy no matter what the called triggers do.
- */
- trigdesc = CopyTriggerDesc(rel->trigdesc);
-
- if (trigdesc == NULL) /* should not happen */
- elog(ERROR, "relation %u has no triggers",
- event->ate_relid);
+ if (estate)
+ {
+ /* Find target relation among estate's result rels */
+ ResultRelInfo *rInfo;
+ int nr;
- /*
- * Allocate space to cache fmgr lookup info for
- * triggers.
- */
- finfo = (FmgrInfo *)
- palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
+ 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 */
+
+ /*
+ * 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);
+
+ /*
+ * Copy relation's trigger info so that we have a
+ * stable copy no matter what the called triggers do.
+ */
+ trigdesc = CopyTriggerDesc(rel->trigdesc);
+
+ 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 */
+ }
}
/*
* 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);
/*
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);
}
* 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;
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--;
{
CommandId firing_id = afterTriggers->firing_counter++;
- afterTriggerInvokeEvents(events, firing_id, true);
+ afterTriggerInvokeEvents(events, firing_id, NULL, true);
}
/*
* 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());
}
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
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,
initResultRelInfo(resultRelInfo,
lfirst_int(l),
rangeTable,
- operation);
+ operation,
+ estate->es_instrument);
resultRelInfo++;
}
}
initResultRelInfo(resultRelInfos,
parseTree->resultRelation,
rangeTable,
- operation);
+ operation,
+ estate->es_instrument);
}
estate->es_result_relations = resultRelInfos;
initResultRelInfo(ResultRelInfo *resultRelInfo,
Index resultRelationIndex,
List *rangeTable,
- CmdType operation)
+ CmdType operation,
+ bool doInstrument)
{
Oid resultRelationOid;
Relation resultRelationDesc;
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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/* Set up instrumentation for this node if requested */
if (estate->es_instrument)
- result->instrument = InstrAlloc();
+ result->instrument = InstrAlloc(1);
return result;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
{
ActiveSnapshot = es->qd->snapshot;
+ AfterTriggerEndQuery(es->qd->estate);
ExecutorEnd(es->qd);
- AfterTriggerEndQuery();
}
PG_CATCH();
{
* 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 $
*
*-------------------------------------------------------------------------
*/
#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;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
{
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
}
}
+ /* 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);
* 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
const char *label, Oid namespace,
List *others);
+extern char *GetConstraintNameForTrigger(Oid triggerId);
+
#endif /* PG_CONSTRAINT_H */
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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
* ----------------
IndexInfo **ri_IndexRelationInfo;
TriggerDesc *ri_TrigDesc;
FmgrInfo *ri_TrigFunctions;
+ struct Instrumentation *ri_TrigInstrument;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
} ResultRelInfo;