aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/trigger.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/trigger.c')
-rw-r--r--src/backend/commands/trigger.c345
1 files changed, 199 insertions, 146 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index ead586a2f21..e585f1517ad 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.258 2010/01/02 16:57:38 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.259 2010/01/17 22:56:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -93,27 +93,27 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
* constraintOid, if nonzero, says that this trigger is being created
* internally to implement that constraint. A suitable pg_depend entry will
* be made to link the trigger to that constraint. constraintOid is zero when
- * executing a user-entered CREATE TRIGGER command.
+ * executing a user-entered CREATE TRIGGER command. (For CREATE CONSTRAINT
+ * TRIGGER, we build a pg_constraint entry internally.)
*
* indexOid, if nonzero, is the OID of an index associated with the constraint.
* We do nothing with this except store it into pg_trigger.tgconstrindid.
*
- * prefix is NULL for user-created triggers. For internally generated
- * constraint triggers, it is a prefix string to use in building the
- * trigger name. (stmt->trigname is the constraint name in such cases.)
+ * If isInternal is true then this is an internally-generated trigger.
+ * This argument sets the tgisinternal field of the pg_trigger entry, and
+ * if TRUE causes us to modify the given trigger name to ensure uniqueness.
*
- * If checkPermissions is true we require ACL_TRIGGER permissions on the
- * relation. If not, the caller already checked permissions. (This is
- * currently redundant with constraintOid being zero, but it's clearer to
- * have a separate argument.)
+ * When isInternal is not true we require ACL_TRIGGER permissions on the
+ * relation. For internal triggers the caller must apply any required
+ * permission checks.
*
* Note: can return InvalidOid if we decided to not create a trigger at all,
* but a foreign-key constraint. This is a kluge for backwards compatibility.
*/
Oid
CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
- Oid constraintOid, Oid indexOid, const char *prefix,
- bool checkPermissions)
+ Oid constraintOid, Oid indexOid,
+ bool isInternal)
{
int16 tgtype;
int ncolumns;
@@ -135,9 +135,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
Oid funcoid;
Oid funcrettype;
Oid trigoid;
- char constrtrigname[NAMEDATALEN];
+ char internaltrigname[NAMEDATALEN];
char *trigname;
- char *constrname;
Oid constrrelid = InvalidOid;
ObjectAddress myself,
referenced;
@@ -160,7 +159,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
constrrelid = RangeVarGetRelid(stmt->constrrel, false);
/* permission checks */
- if (checkPermissions)
+ if (!isInternal)
{
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_TRIGGER);
@@ -338,7 +337,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* convert this legacy representation into a regular foreign key
* constraint. Ugly, but necessary for loading old dump files.
*/
- if (stmt->isconstraint && !OidIsValid(constraintOid) &&
+ if (stmt->isconstraint && !isInternal &&
list_length(stmt->args) >= 6 &&
(list_length(stmt->args) % 2) == 0 &&
RI_FKey_trigger_type(funcoid) != RI_TRIGGER_NONE)
@@ -352,6 +351,41 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
/*
+ * If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a
+ * corresponding pg_constraint entry.
+ */
+ if (stmt->isconstraint && !OidIsValid(constraintOid))
+ {
+ /* Internal callers should have made their own constraints */
+ Assert(!isInternal);
+ constraintOid = CreateConstraintEntry(stmt->trigname,
+ RelationGetNamespace(rel),
+ CONSTRAINT_TRIGGER,
+ stmt->deferrable,
+ stmt->initdeferred,
+ RelationGetRelid(rel),
+ NULL, /* no conkey */
+ 0,
+ InvalidOid, /* no domain */
+ InvalidOid, /* no index */
+ InvalidOid, /* no foreign key */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ ' ',
+ ' ',
+ ' ',
+ NULL, /* no exclusion */
+ NULL, /* no check constraint */
+ NULL,
+ NULL,
+ true, /* islocal */
+ 0); /* inhcount */
+ }
+
+ /*
* Generate the trigger's OID now, so that we can use it in the name if
* needed.
*/
@@ -360,55 +394,52 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
trigoid = GetNewOid(tgrel);
/*
- * If trigger is for a constraint, stmt->trigname is the constraint
- * name; save that and build a unique trigger name based on the supplied
- * prefix, to avoid collisions with user-selected trigger names.
+ * If trigger is internally generated, modify the provided trigger name
+ * to ensure uniqueness by appending the trigger OID. (Callers will
+ * usually supply a simple constant trigger name in these cases.)
*/
- if (prefix != NULL)
+ if (isInternal)
{
- snprintf(constrtrigname, sizeof(constrtrigname),
- "%s_%u", prefix, trigoid);
- trigname = constrtrigname;
- constrname = stmt->trigname;
- }
- else if (stmt->isconstraint)
- {
- /* user constraint trigger: trigger name is also constraint name */
- trigname = stmt->trigname;
- constrname = stmt->trigname;
+ snprintf(internaltrigname, sizeof(internaltrigname),
+ "%s_%u", stmt->trigname, trigoid);
+ trigname = internaltrigname;
}
else
{
- /* regular trigger: use empty constraint name */
+ /* user-defined trigger; use the specified trigger name as-is */
trigname = stmt->trigname;
- constrname = "";
}
/*
* Scan pg_trigger for existing triggers on relation. We do this only to
* give a nice error message if there's already a trigger of the same
* name. (The unique index on tgrelid/tgname would complain anyway.)
+ * We can skip this for internally generated triggers, since the name
+ * modification above should be sufficient.
*
* NOTE that this is cool only because we have AccessExclusiveLock on the
* relation, so the trigger set won't be changing underneath us.
*/
- ScanKeyInit(&key,
- Anum_pg_trigger_tgrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
- tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
- SnapshotNow, 1, &key);
- while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+ if (!isInternal)
{
- Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+ ScanKeyInit(&key,
+ Anum_pg_trigger_tgrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+ SnapshotNow, 1, &key);
+ while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+ {
+ Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
- if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("trigger \"%s\" for relation \"%s\" already exists",
- trigname, stmt->relation->relname)));
+ if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ trigname, stmt->relation->relname)));
+ }
+ systable_endscan(tgscan);
}
- systable_endscan(tgscan);
/*
* Build the new pg_trigger tuple.
@@ -421,9 +452,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
values[Anum_pg_trigger_tgenabled - 1] = CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
- values[Anum_pg_trigger_tgisconstraint - 1] = BoolGetDatum(stmt->isconstraint);
- values[Anum_pg_trigger_tgconstrname - 1] = DirectFunctionCall1(namein,
- CStringGetDatum(constrname));
+ values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal);
values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
@@ -580,12 +609,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
- if (OidIsValid(constraintOid))
+ if (isInternal && OidIsValid(constraintOid))
{
/*
- * It's for a constraint, so make it an internal dependency of the
- * constraint. We can skip depending on the relations, as there'll be
- * an indirect dependency via the constraint.
+ * Internally-generated trigger for a constraint, so make it an
+ * internal dependency of the constraint. We can skip depending on
+ * the relation(s), as there'll be an indirect dependency via the
+ * constraint.
*/
referenced.classId = ConstraintRelationId;
referenced.objectId = constraintOid;
@@ -595,7 +625,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
else
{
/*
- * Regular CREATE TRIGGER, so place dependencies. We make trigger be
+ * User CREATE TRIGGER, so place dependencies. We make trigger be
* auto-dropped if its relation is dropped or if the FK relation is
* dropped. (Auto drop is compatible with our pre-7.3 behavior.)
*/
@@ -612,6 +642,17 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
}
/* Not possible to have an index dependency in this case */
Assert(!OidIsValid(indexOid));
+ /*
+ * If it's a user-specified constraint trigger, make the constraint
+ * internally dependent on the trigger instead of vice versa.
+ */
+ if (OidIsValid(constraintOid))
+ {
+ referenced.classId = ConstraintRelationId;
+ referenced.objectId = constraintOid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL);
+ }
}
/* If column-specific trigger, add normal dependencies on columns */
@@ -1221,7 +1262,7 @@ EnableDisableTrigger(Relation rel, const char *tgname,
{
Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);
- if (OidIsValid(oldtrig->tgconstraint))
+ if (oldtrig->tgisinternal)
{
/* system trigger ... ok to process? */
if (skip_system)
@@ -1341,7 +1382,7 @@ RelationBuildTriggers(Relation relation)
build->tgfoid = pg_trigger->tgfoid;
build->tgtype = pg_trigger->tgtype;
build->tgenabled = pg_trigger->tgenabled;
- build->tgisconstraint = pg_trigger->tgisconstraint;
+ build->tgisinternal = pg_trigger->tgisinternal;
build->tgconstrrelid = pg_trigger->tgconstrrelid;
build->tgconstrindid = pg_trigger->tgconstrindid;
build->tgconstraint = pg_trigger->tgconstraint;
@@ -1699,7 +1740,7 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tgenabled != trig2->tgenabled)
return false;
- if (trig1->tgisconstraint != trig2->tgisconstraint)
+ if (trig1->tgisinternal != trig2->tgisinternal)
return false;
if (trig1->tgconstrrelid != trig2->tgconstrrelid)
return false;
@@ -3838,26 +3879,31 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
}
else
{
+ Relation conrel;
Relation tgrel;
- ListCell *l;
- List *oidlist = NIL;
+ List *conoidlist = NIL;
+ List *tgoidlist = NIL;
+ ListCell *lc;
- /* ----------
+ /*
* Handle SET CONSTRAINTS constraint-name [, ...]
- * First lookup all trigger Oid's for the constraint names.
- * ----------
+ *
+ * First, identify all the named constraints and make a list of their
+ * OIDs. Since, unlike the SQL spec, we allow multiple constraints
+ * of the same name within a schema, the specifications are not
+ * necessarily unique. Our strategy is to target all matching
+ * constraints within the first search-path schema that has any
+ * matches, but disregard matches in schemas beyond the first match.
+ * (This is a bit odd but it's the historical behavior.)
*/
- tgrel = heap_open(TriggerRelationId, AccessShareLock);
+ conrel = heap_open(ConstraintRelationId, AccessShareLock);
- foreach(l, stmt->constraints)
+ foreach(lc, stmt->constraints)
{
- RangeVar *constraint = lfirst(l);
- ScanKeyData skey;
- SysScanDesc tgscan;
- HeapTuple htup;
+ RangeVar *constraint = lfirst(lc);
bool found;
- List *namespaceSearchList;
- ListCell *namespaceSearchCell;
+ List *namespacelist;
+ ListCell *nslc;
if (constraint->catalogname)
{
@@ -3878,94 +3924,49 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
{
Oid namespaceId = LookupExplicitNamespace(constraint->schemaname);
- namespaceSearchList = list_make1_oid(namespaceId);
+ namespacelist = list_make1_oid(namespaceId);
}
else
{
- namespaceSearchList = fetch_search_path(true);
+ namespacelist = fetch_search_path(true);
}
found = false;
- foreach(namespaceSearchCell, namespaceSearchList)
+ foreach(nslc, namespacelist)
{
- Oid searchNamespaceId = lfirst_oid(namespaceSearchCell);
+ Oid namespaceId = lfirst_oid(nslc);
+ SysScanDesc conscan;
+ ScanKeyData skey[2];
+ HeapTuple tup;
- /*
- * Setup to scan pg_trigger by tgconstrname ...
- */
- ScanKeyInit(&skey,
- Anum_pg_trigger_tgconstrname,
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conname,
BTEqualStrategyNumber, F_NAMEEQ,
- PointerGetDatum(constraint->relname));
+ CStringGetDatum(constraint->relname));
+ ScanKeyInit(&skey[1],
+ Anum_pg_constraint_connamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(namespaceId));
- tgscan = systable_beginscan(tgrel, TriggerConstrNameIndexId, true,
- SnapshotNow, 1, &skey);
+ conscan = systable_beginscan(conrel, ConstraintNameNspIndexId,
+ true, SnapshotNow, 2, skey);
- /*
- * ... and search for the constraint trigger row
- */
- while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
+ while (HeapTupleIsValid(tup = systable_getnext(conscan)))
{
- Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
- Oid constraintNamespaceId;
-
- /*
- * Foreign key constraints have triggers on both the
- * parent and child tables. Since these tables may be in
- * different schemas we must pick the child table because
- * that table "owns" the constraint.
- *
- * Referential triggers on the parent table other than
- * NOACTION_DEL and NOACTION_UPD are ignored below, so it
- * is possible to not check them here, but it seems safer
- * to always check.
- */
- if (pg_trigger->tgfoid == F_RI_FKEY_NOACTION_DEL ||
- pg_trigger->tgfoid == F_RI_FKEY_NOACTION_UPD ||
- pg_trigger->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
- pg_trigger->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
- pg_trigger->tgfoid == F_RI_FKEY_CASCADE_UPD ||
- pg_trigger->tgfoid == F_RI_FKEY_CASCADE_DEL ||
- pg_trigger->tgfoid == F_RI_FKEY_SETNULL_UPD ||
- pg_trigger->tgfoid == F_RI_FKEY_SETNULL_DEL ||
- pg_trigger->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
- pg_trigger->tgfoid == F_RI_FKEY_SETDEFAULT_DEL)
- constraintNamespaceId = get_rel_namespace(pg_trigger->tgconstrrelid);
- else
- constraintNamespaceId = get_rel_namespace(pg_trigger->tgrelid);
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
- /*
- * If this constraint is not in the schema we're currently
- * searching for, keep looking.
- */
- if (constraintNamespaceId != searchNamespaceId)
- continue;
-
- /*
- * If we found some, check that they fit the deferrability
- * but skip referential action ones, since they are
- * silently never deferrable.
- */
- if (pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_UPD &&
- pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_DEL &&
- pg_trigger->tgfoid != F_RI_FKEY_CASCADE_UPD &&
- pg_trigger->tgfoid != F_RI_FKEY_CASCADE_DEL &&
- pg_trigger->tgfoid != F_RI_FKEY_SETNULL_UPD &&
- pg_trigger->tgfoid != F_RI_FKEY_SETNULL_DEL &&
- pg_trigger->tgfoid != F_RI_FKEY_SETDEFAULT_UPD &&
- pg_trigger->tgfoid != F_RI_FKEY_SETDEFAULT_DEL)
- {
- if (stmt->deferred && !pg_trigger->tgdeferrable)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("constraint \"%s\" is not deferrable",
- constraint->relname)));
- oidlist = lappend_oid(oidlist, HeapTupleGetOid(htup));
- }
+ if (con->condeferrable)
+ conoidlist = lappend_oid(conoidlist,
+ HeapTupleGetOid(tup));
+ else if (stmt->deferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint \"%s\" is not deferrable",
+ constraint->relname)));
found = true;
}
- systable_endscan(tgscan);
+ systable_endscan(conscan);
/*
* Once we've found a matching constraint we do not search
@@ -3973,10 +3974,9 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
*/
if (found)
break;
-
}
- list_free(namespaceSearchList);
+ list_free(namespacelist);
/*
* Not found ?
@@ -3987,14 +3987,67 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
errmsg("constraint \"%s\" does not exist",
constraint->relname)));
}
+
+ heap_close(conrel, AccessShareLock);
+
+ /*
+ * Now, locate the trigger(s) implementing each of these constraints,
+ * and make a list of their OIDs.
+ */
+ tgrel = heap_open(TriggerRelationId, AccessShareLock);
+
+ foreach(lc, conoidlist)
+ {
+ Oid conoid = lfirst_oid(lc);
+ bool found;
+ ScanKeyData skey;
+ SysScanDesc tgscan;
+ HeapTuple htup;
+
+ found = false;
+
+ ScanKeyInit(&skey,
+ Anum_pg_trigger_tgconstraint,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(conoid));
+
+ tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
+ SnapshotNow, 1, &skey);
+
+ while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
+ {
+ Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
+
+ /*
+ * Silently skip triggers that are marked as non-deferrable
+ * in pg_trigger. This is not an error condition, since
+ * a deferrable RI constraint may have some non-deferrable
+ * actions.
+ */
+ if (pg_trigger->tgdeferrable)
+ tgoidlist = lappend_oid(tgoidlist,
+ HeapTupleGetOid(htup));
+
+ found = true;
+ }
+
+ systable_endscan(tgscan);
+
+ /* Safety check: a deferrable constraint should have triggers */
+ if (!found)
+ elog(ERROR, "no triggers found for constraint with OID %u",
+ conoid);
+ }
+
heap_close(tgrel, AccessShareLock);
/*
- * Set the trigger states of individual triggers for this xact.
+ * Now we can set the trigger states of individual triggers for this
+ * xact.
*/
- foreach(l, oidlist)
+ foreach(lc, tgoidlist)
{
- Oid tgoid = lfirst_oid(l);
+ Oid tgoid = lfirst_oid(lc);
SetConstraintState state = afterTriggers->state;
bool found = false;
int i;