diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 39e8073f55b..c9d454bc497 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.164 2007/05/25 17:54:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.165 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -52,6 +52,8 @@ typedef struct ExplainState static void ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString, ParamListInfo params, TupOutputState *tstate); +static void report_triggers(ResultRelInfo *rInfo, bool show_relname, + StringInfo buf); static double elapsed_time(instr_time *starttime); static void explain_outNode(StringInfo str, Plan *plan, PlanState *planstate, @@ -310,50 +312,21 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, if (es->printAnalyze) { ResultRelInfo *rInfo; + bool show_relname; int numrels = queryDesc->estate->es_num_result_relations; + List *targrels = queryDesc->estate->es_trig_target_relations; int nr; + ListCell *l; + show_relname = (numrels > 1 || targrels != NIL); rInfo = queryDesc->estate->es_result_relations; for (nr = 0; nr < numrels; rInfo++, nr++) + report_triggers(rInfo, show_relname, &buf); + + foreach(l, targrels) { - int nt; - - if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) - 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 (OidIsValid(trig->tgconstraint) && - (conname = get_constraint_name(trig->tgconstraint)) != NULL) - { - appendStringInfo(&buf, "Trigger for constraint %s", - conname); - pfree(conname); - } - else - appendStringInfo(&buf, "Trigger %s", trig->tgname); - - if (numrels > 1) - appendStringInfo(&buf, " on %s", - RelationGetRelationName(rInfo->ri_RelationDesc)); - - appendStringInfo(&buf, ": time=%.3f calls=%.0f\n", - 1000.0 * instr->total, - instr->ntuples); - } + rInfo = (ResultRelInfo *) lfirst(l); + report_triggers(rInfo, show_relname, &buf); } } @@ -382,6 +355,51 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, pfree(es); } +/* + * report_triggers - + * report execution stats for a single relation's triggers + */ +static void +report_triggers(ResultRelInfo *rInfo, bool show_relname, StringInfo buf) +{ + int nt; + + if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument) + return; + 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 (OidIsValid(trig->tgconstraint) && + (conname = get_constraint_name(trig->tgconstraint)) != NULL) + { + appendStringInfo(buf, "Trigger for constraint %s", conname); + pfree(conname); + } + else + appendStringInfo(buf, "Trigger %s", trig->tgname); + + if (show_relname) + appendStringInfo(buf, " on %s", + RelationGetRelationName(rInfo->ri_RelationDesc)); + + appendStringInfo(buf, ": time=%.3f calls=%.0f\n", + 1000.0 * instr->total, instr->ntuples); + } +} + /* Compute elapsed time in seconds since given timestamp */ static double elapsed_time(instr_time *starttime) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 2788684d1ab..afcdaa5e913 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.217 2007/08/15 19:15:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.218 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2313,11 +2313,10 @@ 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. + * If estate isn't NULL, we use its result relation info to avoid repeated + * openings and closing of trigger target relations. If it is NULL, we + * make one locally to cache the info in case there are multiple trigger + * events per rel. * * When delete_ok is TRUE, it's okay to delete fully-processed events. * The events list pointers are updated. @@ -2332,12 +2331,19 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, AfterTriggerEvent event, prev_event; MemoryContext per_tuple_context; - bool locally_opened = false; + bool local_estate = false; Relation rel = NULL; TriggerDesc *trigdesc = NULL; FmgrInfo *finfo = NULL; Instrumentation *instr = NULL; + /* Make a local EState if need be */ + if (estate == NULL) + { + estate = CreateExecutorState(); + local_estate = true; + } + /* Make a per-tuple memory context for trigger function calls */ per_tuple_context = AllocSetContextCreate(CurrentMemoryContext, @@ -2360,77 +2366,21 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, event->ate_firing_id == firing_id) { /* - * So let's fire it... but first, open the correct relation if + * So let's fire it... but first, find the correct relation if * this is not the same relation as before. */ - if (rel == NULL || rel->rd_id != event->ate_relid) + if (rel == NULL || RelationGetRelid(rel) != event->ate_relid) { - if (locally_opened) - { - /* close prior rel if any */ - if (rel) - heap_close(rel, NoLock); - if (trigdesc) - FreeTriggerDesc(trigdesc); - if (finfo) - pfree(finfo); - Assert(instr == NULL); /* never used in this case */ - } - locally_opened = true; + ResultRelInfo *rInfo; - if (estate) - { - /* Find target relation among estate's result rels */ - ResultRelInfo *rInfo; - int nr; - - rInfo = estate->es_result_relations; - nr = estate->es_num_result_relations; - while (nr > 0) - { - if (rInfo->ri_RelationDesc->rd_id == event->ate_relid) - { - rel = rInfo->ri_RelationDesc; - trigdesc = rInfo->ri_TrigDesc; - finfo = rInfo->ri_TrigFunctions; - instr = rInfo->ri_TrigInstrument; - locally_opened = false; - break; - } - rInfo++; - nr--; - } - } - - if (locally_opened) - { - /* Hard way: open target relation for ourselves */ - - /* - * 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 */ - instr = NULL; - } + rInfo = ExecGetTriggerResultRel(estate, event->ate_relid); + rel = rInfo->ri_RelationDesc; + trigdesc = rInfo->ri_TrigDesc; + finfo = rInfo->ri_TrigFunctions; + instr = rInfo->ri_TrigInstrument; + if (trigdesc == NULL) /* should not happen */ + elog(ERROR, "relation %u has no triggers", + event->ate_relid); } /* @@ -2480,17 +2430,22 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, events->tail = prev_event; /* Release working resources */ - if (locally_opened) - { - 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); + + if (local_estate) + { + ListCell *l; + + foreach(l, estate->es_trig_target_relations) + { + ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l); + + /* Close indices and then the relation itself */ + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + } + FreeExecutorState(estate); + } } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index dfd1a84ab77..322c1d1a27d 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.295 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.296 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -66,8 +66,8 @@ typedef struct evalPlanQual /* decls for local routines only used within this module */ static void InitPlan(QueryDesc *queryDesc, int eflags); static void initResultRelInfo(ResultRelInfo *resultRelInfo, + Relation resultRelationDesc, Index resultRelationIndex, - List *rangeTable, CmdType operation, bool doInstrument); static void ExecEndPlan(PlanState *planstate, EState *estate); @@ -494,9 +494,15 @@ InitPlan(QueryDesc *queryDesc, int eflags) resultRelInfo = resultRelInfos; foreach(l, resultRelations) { + Index resultRelationIndex = lfirst_int(l); + Oid resultRelationOid; + Relation resultRelation; + + resultRelationOid = getrelid(resultRelationIndex, rangeTable); + resultRelation = heap_open(resultRelationOid, RowExclusiveLock); initResultRelInfo(resultRelInfo, - lfirst_int(l), - rangeTable, + resultRelation, + resultRelationIndex, operation, estate->es_instrument); resultRelInfo++; @@ -831,19 +837,20 @@ InitPlan(QueryDesc *queryDesc, int eflags) */ static void initResultRelInfo(ResultRelInfo *resultRelInfo, + Relation resultRelationDesc, Index resultRelationIndex, - List *rangeTable, CmdType operation, bool doInstrument) { - Oid resultRelationOid; - Relation resultRelationDesc; - - resultRelationOid = getrelid(resultRelationIndex, rangeTable); - resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock); - + /* + * Check valid relkind ... parser and/or planner should have noticed + * this already, but let's make sure. + */ switch (resultRelationDesc->rd_rel->relkind) { + case RELKIND_RELATION: + /* OK */ + break; case RELKIND_SEQUENCE: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -862,8 +869,15 @@ initResultRelInfo(ResultRelInfo *resultRelInfo, errmsg("cannot change view \"%s\"", RelationGetRelationName(resultRelationDesc)))); break; + default: + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change relation \"%s\"", + RelationGetRelationName(resultRelationDesc)))); + break; } + /* OK, fill in the node */ MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); resultRelInfo->type = T_ResultRelInfo; resultRelInfo->ri_RangeTableIndex = resultRelationIndex; @@ -904,6 +918,76 @@ initResultRelInfo(ResultRelInfo *resultRelInfo, ExecOpenIndices(resultRelInfo); } +/* + * ExecGetTriggerResultRel + * + * Get a ResultRelInfo for a trigger target relation. Most of the time, + * triggers are fired on one of the result relations of the query, and so + * we can just return a member of the es_result_relations array. (Note: in + * self-join situations there might be multiple members with the same OID; + * if so it doesn't matter which one we pick.) However, it is sometimes + * necessary to fire triggers on other relations; this happens mainly when an + * RI update trigger queues additional triggers on other relations, which will + * be processed in the context of the outer query. For efficiency's sake, + * we want to have a ResultRelInfo for those triggers too; that can avoid + * repeated re-opening of the relation. (It also provides a way for EXPLAIN + * ANALYZE to report the runtimes of such triggers.) So we make additional + * ResultRelInfo's as needed, and save them in es_trig_target_relations. + */ +ResultRelInfo * +ExecGetTriggerResultRel(EState *estate, Oid relid) +{ + ResultRelInfo *rInfo; + int nr; + ListCell *l; + Relation rel; + MemoryContext oldcontext; + + /* First, search through the query result relations */ + rInfo = estate->es_result_relations; + nr = estate->es_num_result_relations; + while (nr > 0) + { + if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + return rInfo; + rInfo++; + nr--; + } + /* Nope, but maybe we already made an extra ResultRelInfo for it */ + foreach(l, estate->es_trig_target_relations) + { + rInfo = (ResultRelInfo *) lfirst(l); + if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + return rInfo; + } + /* Nope, so we need a new one */ + + /* + * Open the target relation's relcache entry. We assume that an + * appropriate lock is still held by the backend from whenever the + * trigger event got queued, so we need take no new lock here. + */ + rel = heap_open(relid, NoLock); + + /* + * Make the new entry in the right context. Currently, we don't need + * any index information in ResultRelInfos used only for triggers, + * so tell initResultRelInfo it's a DELETE. + */ + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + rInfo = makeNode(ResultRelInfo); + initResultRelInfo(rInfo, + rel, + 0, /* dummy rangetable index */ + CMD_DELETE, + estate->es_instrument); + estate->es_trig_target_relations = + lappend(estate->es_trig_target_relations, rInfo); + MemoryContextSwitchTo(oldcontext); + + return rInfo; +} + /* * ExecContextForcesOids * @@ -1019,6 +1103,17 @@ ExecEndPlan(PlanState *planstate, EState *estate) resultRelInfo++; } + /* + * likewise close any trigger target relations + */ + foreach(l, estate->es_trig_target_relations) + { + resultRelInfo = (ResultRelInfo *) lfirst(l); + /* Close indices and then the relation itself */ + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + } + /* * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks */ @@ -2267,6 +2362,7 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq) epqstate->es_num_result_relations = estate->es_num_result_relations; epqstate->es_result_relation_info = estate->es_result_relation_info; epqstate->es_junkFilter = estate->es_junkFilter; + /* es_trig_target_relations must NOT be copied */ epqstate->es_into_relation_descriptor = estate->es_into_relation_descriptor; epqstate->es_into_relation_use_wal = estate->es_into_relation_use_wal; epqstate->es_param_list_info = estate->es_param_list_info; @@ -2331,7 +2427,8 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq) * * This is a cut-down version of ExecutorEnd(); basically we want to do most * of the normal cleanup, but *not* close result relations (which we are - * just sharing from the outer query). + * just sharing from the outer query). We do, however, have to close any + * trigger target relations that got opened, since those are not shared. */ static void EvalPlanQualStop(evalPlanQual *epq) @@ -2360,6 +2457,15 @@ EvalPlanQualStop(evalPlanQual *epq) epqstate->es_evTuple[epq->rti - 1] = NULL; } + foreach(l, epqstate->es_trig_target_relations) + { + ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l); + + /* Close indices and then the relation itself */ + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + } + MemoryContextSwitchTo(oldcontext); FreeExecutorState(epqstate); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index fe76a337747..1d478062998 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.149 2007/07/31 16:36:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.150 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -184,6 +184,7 @@ CreateExecutorState(void) estate->es_junkFilter = NULL; + estate->es_trig_target_relations = NIL; estate->es_trig_tuple_slot = NULL; estate->es_into_relation_descriptor = NULL; diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 539e2f6fca7..f4272cb758b 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.141 2007/06/11 22:22:42 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.142 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -138,6 +138,7 @@ extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count); extern void ExecutorEnd(QueryDesc *queryDesc); extern void ExecutorRewind(QueryDesc *queryDesc); +extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids); extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ccc243cfd06..d886c0149fc 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.176 2007/06/05 21:31:08 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.177 2007/08/15 21:39:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -310,9 +310,12 @@ typedef struct EState ResultRelInfo *es_result_relation_info; /* currently active array elt */ JunkFilter *es_junkFilter; /* currently active junk filter */ + /* Stuff used for firing triggers: */ + List *es_trig_target_relations; /* trigger-only ResultRelInfos */ TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */ - Relation es_into_relation_descriptor; /* for SELECT INTO */ + /* Stuff used for SELECT INTO: */ + Relation es_into_relation_descriptor; bool es_into_relation_use_wal; /* Parameter info: */