* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.134 2002/10/03 21:06:23 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.135 2002/10/14 16:51:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
HeapTuple oldtup, HeapTuple newtup);
static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
- Relation rel, FmgrInfo *finfo,
+ Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
MemoryContext per_tuple_context);
/*
* Build trigger data to attach to the given relcache entry.
*
- * Note that trigger data must be allocated in CacheMemoryContext
- * to ensure it survives as long as the relcache entry. But we
- * are probably running in a less long-lived working context.
+ * Note that trigger data attached to a relcache entry must be stored in
+ * CacheMemoryContext to ensure it survives as long as the relcache entry.
+ * But we should be running in a less long-lived working context. To avoid
+ * leaking cache memory if this routine fails partway through, we build a
+ * temporary TriggerDesc in working memory and then copy the completed
+ * structure into cache memory.
*/
void
RelationBuildTriggers(Relation relation)
ScanKeyData skey;
SysScanDesc tgscan;
HeapTuple htup;
+ MemoryContext oldContext;
+
+ Assert(ntrigs > 0); /* else I should not have been called */
- triggers = (Trigger *) MemoryContextAlloc(CacheMemoryContext,
- ntrigs * sizeof(Trigger));
+ triggers = (Trigger *) palloc(ntrigs * sizeof(Trigger));
/*
* Note: since we scan the triggers using TriggerRelidNameIndex, we
build = &(triggers[found]);
build->tgoid = HeapTupleGetOid(htup);
- build->tgname = MemoryContextStrdup(CacheMemoryContext,
- DatumGetCString(DirectFunctionCall1(nameout,
- NameGetDatum(&pg_trigger->tgname))));
+ build->tgname = DatumGetCString(DirectFunctionCall1(nameout,
+ NameGetDatum(&pg_trigger->tgname)));
build->tgfoid = pg_trigger->tgfoid;
build->tgtype = pg_trigger->tgtype;
build->tgenabled = pg_trigger->tgenabled;
elog(ERROR, "RelationBuildTriggers: tgargs IS NULL for rel %s",
RelationGetRelationName(relation));
p = (char *) VARDATA(val);
- build->tgargs = (char **)
- MemoryContextAlloc(CacheMemoryContext,
- build->tgnargs * sizeof(char *));
+ build->tgargs = (char **) palloc(build->tgnargs * sizeof(char *));
for (i = 0; i < build->tgnargs; i++)
{
- build->tgargs[i] = MemoryContextStrdup(CacheMemoryContext,
- p);
+ build->tgargs[i] = pstrdup(p);
p += strlen(p) + 1;
}
}
RelationGetRelationName(relation));
/* Build trigdesc */
- trigdesc = (TriggerDesc *) MemoryContextAlloc(CacheMemoryContext,
- sizeof(TriggerDesc));
+ trigdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
MemSet(trigdesc, 0, sizeof(TriggerDesc));
trigdesc->triggers = triggers;
trigdesc->numtriggers = ntrigs;
for (found = 0; found < ntrigs; found++)
InsertTrigger(trigdesc, &(triggers[found]), found);
- relation->trigdesc = trigdesc;
+ /* Copy completed trigdesc into cache storage */
+ oldContext = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->trigdesc = CopyTriggerDesc(trigdesc);
+ MemoryContextSwitchTo(oldContext);
+
+ /* Release working memory */
+ FreeTriggerDesc(trigdesc);
}
-/* Insert the given trigger into the appropriate index list(s) for it */
+/*
+ * Insert the given trigger into the appropriate index list(s) for it
+ *
+ * To simplify storage management, we allocate each index list at the max
+ * possible size (trigdesc->numtriggers) if it's used at all. This does
+ * not waste space permanently since we're only building a temporary
+ * trigdesc at this point.
+ */
static void
InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx)
{
{
tp = &(t[TRIGGER_EVENT_INSERT]);
if (*tp == NULL)
- *tp = (int *) MemoryContextAlloc(CacheMemoryContext,
- sizeof(int));
- else
- *tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_INSERT] + 1) *
- sizeof(int));
+ *tp = (int *) palloc(trigdesc->numtriggers * sizeof(int));
(*tp)[n[TRIGGER_EVENT_INSERT]] = indx;
(n[TRIGGER_EVENT_INSERT])++;
}
{
tp = &(t[TRIGGER_EVENT_DELETE]);
if (*tp == NULL)
- *tp = (int *) MemoryContextAlloc(CacheMemoryContext,
- sizeof(int));
- else
- *tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_DELETE] + 1) *
- sizeof(int));
+ *tp = (int *) palloc(trigdesc->numtriggers * sizeof(int));
(*tp)[n[TRIGGER_EVENT_DELETE]] = indx;
(n[TRIGGER_EVENT_DELETE])++;
}
{
tp = &(t[TRIGGER_EVENT_UPDATE]);
if (*tp == NULL)
- *tp = (int *) MemoryContextAlloc(CacheMemoryContext,
- sizeof(int));
- else
- *tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_UPDATE] + 1) *
- sizeof(int));
+ *tp = (int *) palloc(trigdesc->numtriggers * sizeof(int));
(*tp)[n[TRIGGER_EVENT_UPDATE]] = indx;
(n[TRIGGER_EVENT_UPDATE])++;
}
}
+/*
+ * Copy a TriggerDesc data structure.
+ *
+ * The copy is allocated in the current memory context.
+ */
+TriggerDesc *
+CopyTriggerDesc(TriggerDesc *trigdesc)
+{
+ TriggerDesc *newdesc;
+ uint16 *n;
+ int **t,
+ *tnew;
+ Trigger *trigger;
+ int i;
+
+ if (trigdesc == NULL || trigdesc->numtriggers <= 0)
+ return NULL;
+
+ newdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
+ memcpy(newdesc, trigdesc, sizeof(TriggerDesc));
+
+ trigger = (Trigger *) palloc(trigdesc->numtriggers * sizeof(Trigger));
+ memcpy(trigger, trigdesc->triggers,
+ trigdesc->numtriggers * sizeof(Trigger));
+ newdesc->triggers = trigger;
+
+ for (i = 0; i < trigdesc->numtriggers; i++)
+ {
+ trigger->tgname = pstrdup(trigger->tgname);
+ if (trigger->tgnargs > 0)
+ {
+ char **newargs;
+ int16 j;
+
+ newargs = (char **) palloc(trigger->tgnargs * sizeof(char *));
+ for (j = 0; j < trigger->tgnargs; j++)
+ newargs[j] = pstrdup(trigger->tgargs[j]);
+ trigger->tgargs = newargs;
+ }
+ trigger++;
+ }
+
+ n = newdesc->n_before_statement;
+ t = newdesc->tg_before_statement;
+ for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ {
+ if (n[i] > 0)
+ {
+ tnew = (int *) palloc(n[i] * sizeof(int));
+ memcpy(tnew, t[i], n[i] * sizeof(int));
+ t[i] = tnew;
+ }
+ else
+ t[i] = NULL;
+ }
+ n = newdesc->n_before_row;
+ t = newdesc->tg_before_row;
+ for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ {
+ if (n[i] > 0)
+ {
+ tnew = (int *) palloc(n[i] * sizeof(int));
+ memcpy(tnew, t[i], n[i] * sizeof(int));
+ t[i] = tnew;
+ }
+ else
+ t[i] = NULL;
+ }
+ n = newdesc->n_after_row;
+ t = newdesc->tg_after_row;
+ for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ {
+ if (n[i] > 0)
+ {
+ tnew = (int *) palloc(n[i] * sizeof(int));
+ memcpy(tnew, t[i], n[i] * sizeof(int));
+ t[i] = tnew;
+ }
+ else
+ t[i] = NULL;
+ }
+ n = newdesc->n_after_statement;
+ t = newdesc->tg_after_statement;
+ for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ {
+ if (n[i] > 0)
+ {
+ tnew = (int *) palloc(n[i] * sizeof(int));
+ memcpy(tnew, t[i], n[i] * sizeof(int));
+ t[i] = tnew;
+ }
+ else
+ t[i] = NULL;
+ }
+
+ return newdesc;
+}
+
+/*
+ * Free a TriggerDesc data structure.
+ */
void
FreeTriggerDesc(TriggerDesc *trigdesc)
{
pfree(trigdesc);
}
+/*
+ * Compare two TriggerDesc structures for logical equality.
+ */
+#ifdef NOT_USED
bool
equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
{
return false;
return true;
}
+#endif /* NOT_USED */
/*
* Call a trigger function.
* event: event currently being fired.
* itemno: item within event currently being fired.
* rel: open relation for event.
- * finfo: array of fmgr lookup cache entries (one per trigger of relation).
+ * trigdesc: working copy of rel's trigger info.
+ * finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
* per_tuple_context: memory context to call trigger function in.
* ----------
*/
static void
DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
- Relation rel, FmgrInfo *finfo,
+ Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
MemoryContext per_tuple_context)
{
Oid tgoid = event->dte_item[itemno].dti_tgoid;
- TriggerDesc *trigdesc = rel->trigdesc;
TriggerData LocTriggerData;
HeapTupleData oldtuple;
HeapTupleData newtuple;
}
/*
- * Call the trigger and throw away an eventually returned updated
+ * Call the trigger and throw away any eventually returned updated
* tuple.
*/
rettuple = ExecCallTriggerFunc(&LocTriggerData,
prev_event = NULL;
MemoryContext per_tuple_context;
Relation rel = NULL;
+ TriggerDesc *trigdesc = NULL;
FmgrInfo *finfo = NULL;
/*
{
if (rel)
heap_close(rel, NoLock);
+ FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
rel = heap_open(event->dte_relid, NoLock);
/*
- * Allocate space to cache fmgr lookup info for
- * triggers of this relation.
+ * 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)
+ elog(ERROR, "deferredTriggerInvokeEvents: relation %u has no triggers",
+ event->dte_relid);
+
+ /*
+ * Allocate space to cache fmgr lookup info for triggers.
*/
finfo = (FmgrInfo *)
- palloc(rel->trigdesc->numtriggers * sizeof(FmgrInfo));
+ palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
MemSet(finfo, 0,
- rel->trigdesc->numtriggers * sizeof(FmgrInfo));
+ trigdesc->numtriggers * sizeof(FmgrInfo));
}
- DeferredTriggerExecute(event, i, rel, finfo,
+ DeferredTriggerExecute(event, i, rel, trigdesc, finfo,
per_tuple_context);
event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
/* Release working resources */
if (rel)
heap_close(rel, NoLock);
+ FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
MemoryContextDelete(per_tuple_context);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.176 2002/09/22 20:56:28 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.177 2002/10/14 16:51:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* Free all the subsidiary data structures of the relcache entry. We
* cannot free rd_att if we are trying to rebuild the entry, however,
- * because pointers to it may be cached in various places. The trigger
- * manager might also have pointers into the trigdesc, and the rule
- * manager might have pointers into the rewrite rules. So to begin
+ * because pointers to it may be cached in various places. The rule
+ * manager might also have pointers into the rewrite rules. So to begin
* with, we can only get rid of these fields:
*/
+ FreeTriggerDesc(relation->trigdesc);
if (relation->rd_index)
pfree(relation->rd_index);
if (relation->rd_am)
FreeTupleDesc(relation->rd_att);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
- FreeTriggerDesc(relation->trigdesc);
pfree(relation);
}
else
{
/*
* When rebuilding an open relcache entry, must preserve ref count
- * and rd_isnew flag. Also attempt to preserve the tupledesc,
- * rewrite rules, and trigger substructures in place.
+ * and rd_isnew flag. Also attempt to preserve the tupledesc and
+ * rewrite-rule substructures in place.
*/
int old_refcnt = relation->rd_refcnt;
bool old_isnew = relation->rd_isnew;
TupleDesc old_att = relation->rd_att;
RuleLock *old_rules = relation->rd_rules;
MemoryContext old_rulescxt = relation->rd_rulescxt;
- TriggerDesc *old_trigdesc = relation->trigdesc;
RelationBuildDescInfo buildinfo;
buildinfo.infotype = INFO_RELID;
FreeTupleDesc(old_att);
if (old_rulescxt)
MemoryContextDelete(old_rulescxt);
- FreeTriggerDesc(old_trigdesc);
pfree(relation);
elog(ERROR, "RelationClearRelation: relation %u deleted while still in use",
buildinfo.i.info_id);
if (old_rulescxt)
MemoryContextDelete(old_rulescxt);
}
- if (equalTriggerDescs(old_trigdesc, relation->trigdesc))
- {
- FreeTriggerDesc(relation->trigdesc);
- relation->trigdesc = old_trigdesc;
- }
- else
- FreeTriggerDesc(old_trigdesc);
/*
* Update rd_nblocks. This is kind of expensive, but I think we