diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml index 2316df99a5..ff93eaf831 100644 --- a/doc/src/sgml/perform.sgml +++ b/doc/src/sgml/perform.sgml @@ -1,5 +1,5 @@ @@ -320,16 +320,19 @@ EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 < 50 AND t1 - The Total runtime shown by EXPLAIN ANALYZE 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 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 INSERT, UPDATE, and 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 Total runtime shown by EXPLAIN + ANALYZE 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 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 INSERT, + UPDATE, and 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. diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 552fd391fe..d76c956d14 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -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; +} diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index d193d1dd31..3713a039b4 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -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); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 02c9534cab..02ceedd395 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -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 */ diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index ca72503d04..3d5133f825 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -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 diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 54e0eac268..277d874448 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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()); } } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index ad3ff3e5b9..304f1ba6b1 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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; diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 2bd4b7e6fc..71b274530c 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -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; } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index df54b56ff5..fdbd6107a0 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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(); { diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c index 8b9b6abb93..52d9de4f4a 100644 --- a/src/backend/executor/instrument.c +++ b/src/backend/executor/instrument.c @@ -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; } diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index d26e306850..de119ec776 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -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) { diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 1f66fda2b7..51f3df8e56 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -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); diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 3231f4b778..fdc8c0676d 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -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 */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1f84b0eaa9..1aacccc811 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -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); diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h index 8936c5cce2..0540fd0da7 100644 --- a/src/include/executor/instrument.h +++ b/src/include/executor/instrument.h @@ -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); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 8e5404b507..da82daaac9 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -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;