diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index ca90cd56ca..f917652a08 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ - + @@ -1721,6 +1721,11 @@ catalog, not here. + + User-defined constraint triggers (created with CREATE CONSTRAINT + TRIGGER) also give rise to an entry in this table. + + Check constraints on domains are stored here, too. @@ -1764,6 +1769,7 @@ f = foreign key constraint, p = primary key constraint, u = unique constraint, + t = constraint trigger, x = exclusion constraint @@ -1873,7 +1879,8 @@ conkey int2[] pg_attribute.attnum - If a table constraint (including a foreign key), list of the constrained columns + If a table constraint (including foreign keys, but not constraint + triggers), list of the constrained columns @@ -4826,17 +4833,11 @@ - tgisconstraint + tgisinternal bool - True if trigger is a constraint trigger - - - - tgconstrname - name - - Constraint name, if a constraint trigger + True if trigger is internally generated (usually, to enforce + the constraint identified by tgconstraint) @@ -4857,7 +4858,7 @@ tgconstraint oid pg_constraint.oid - The pg_constraint entry owning the trigger, if any + The pg_constraint entry associated with the trigger, if any @@ -4919,13 +4920,12 @@ When tgconstraint is nonzero, - tgisconstraint must be true, and - tgconstrname, tgconstrrelid, - tgconstrindid, - tgdeferrable, tginitdeferred are redundant - with the referenced pg_constraint entry. The reason we - keep these fields is that we support stand-alone constraint - triggers with no corresponding pg_constraint entry. + tgconstrrelid, tgconstrindid, + tgdeferrable, and tginitdeferred are + largely redundant with the referenced pg_constraint entry. + However, it is possible for a non-deferrable trigger to be associated + with a deferrable constraint: foreign key constraints can have some + deferrable and some non-deferrable triggers. diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index 49571ca7c5..5418f314a3 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -1,4 +1,4 @@ - + Triggers @@ -506,7 +506,7 @@ typedef struct Trigger Oid tgfoid; int16 tgtype; bool tgenabled; - bool tgisconstraint; + bool tgisinternal; Oid tgconstrrelid; Oid tgconstrindid; Oid tgconstraint; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 7a6d914567..b84c586a99 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.329 2010/01/06 03:03:58 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.330 2010/01/17 22:56:21 tgl Exp $ * * * INTERFACE ROUTINES @@ -825,7 +825,8 @@ index_create(Oid heapRelationId, -1); trigger = makeNode(CreateTrigStmt); - trigger->trigname = pstrdup(indexRelationName); + trigger->trigname = (isprimary ? "PK_ConstraintTrigger" : + "Unique_ConstraintTrigger"); trigger->relation = heapRel; trigger->funcname = SystemFuncName("unique_key_recheck"); trigger->args = NIL; @@ -840,9 +841,7 @@ index_create(Oid heapRelationId, trigger->constrrel = NULL; (void) CreateTrigger(trigger, NULL, conOid, indexRelationId, - isprimary ? "PK_ConstraintTrigger" : - "Unique_ConstraintTrigger", - false); + true); } } else diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 1b413626dd..64417b3ba8 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -4,7 +4,7 @@ * * Copyright (c) 2003-2010, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.63 2010/01/02 16:57:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.64 2010/01/17 22:56:21 tgl Exp $ */ /* @@ -1666,7 +1666,7 @@ CREATE VIEW table_constraints AS WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace AND c.conrelid = r.oid - AND c.contype <> 'x' -- ignore nonstandard exclusion constraints + AND c.contype NOT IN ('t', 'x') -- ignore nonstandard constraints AND r.relkind = 'r' AND (NOT pg_is_other_temp_schema(nr.oid)) AND (pg_has_role(r.relowner, 'USAGE') @@ -1868,7 +1868,7 @@ CREATE VIEW triggered_update_columns AS AND c.oid = t.tgrelid AND t.oid = ta.tgoid AND (a.attrelid, a.attnum) = (t.tgrelid, ta.tgattnum) - AND NOT t.tgisconstraint + AND NOT t.tgisinternal AND (NOT pg_is_other_temp_schema(n.oid)) AND (pg_has_role(c.relowner, 'USAGE') -- SELECT privilege omitted, per SQL standard @@ -1953,7 +1953,7 @@ CREATE VIEW triggers AS WHERE n.oid = c.relnamespace AND c.oid = t.tgrelid AND t.tgtype & em.num <> 0 - AND NOT t.tgisconstraint + AND NOT t.tgisinternal AND (NOT pg_is_other_temp_schema(n.oid)) AND (pg_has_role(c.relowner, 'USAGE') -- SELECT privilege omitted, per SQL standard diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index bf1234614c..43d8c0c56b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.315 2010/01/15 09:19:01 heikki Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.316 2010/01/17 22:56:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -5338,7 +5338,7 @@ validateForeignKeyConstraint(Constraint *fkconstraint, trig.tgoid = InvalidOid; trig.tgname = fkconstraint->conname; trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN; - trig.tgisconstraint = TRUE; + trig.tgisinternal = TRUE; trig.tgconstrrelid = RelationGetRelid(pkrel); trig.tgconstrindid = pkindOid; trig.tgconstraint = constraintOid; @@ -5399,7 +5399,7 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint, CreateTrigStmt *fk_trigger; fk_trigger = makeNode(CreateTrigStmt); - fk_trigger->trigname = fkconstraint->conname; + fk_trigger->trigname = "RI_ConstraintTrigger"; fk_trigger->relation = myRel; fk_trigger->before = false; fk_trigger->row = true; @@ -5424,8 +5424,7 @@ CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint, fk_trigger->constrrel = fkconstraint->pktable; fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, - "RI_ConstraintTrigger", false); + (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -5463,7 +5462,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, * DELETE action on the referenced table. */ fk_trigger = makeNode(CreateTrigStmt); - fk_trigger->trigname = fkconstraint->conname; + fk_trigger->trigname = "RI_ConstraintTrigger"; fk_trigger->relation = fkconstraint->pktable; fk_trigger->before = false; fk_trigger->row = true; @@ -5506,8 +5505,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, } fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, - "RI_ConstraintTrigger", false); + (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -5517,7 +5515,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, * UPDATE action on the referenced table. */ fk_trigger = makeNode(CreateTrigStmt); - fk_trigger->trigname = fkconstraint->conname; + fk_trigger->trigname = "RI_ConstraintTrigger"; fk_trigger->relation = fkconstraint->pktable; fk_trigger->before = false; fk_trigger->row = true; @@ -5560,8 +5558,7 @@ createForeignKeyTriggers(Relation rel, Constraint *fkconstraint, } fk_trigger->args = NIL; - (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, - "RI_ConstraintTrigger", false); + (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true); } /* diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ead586a2f2..e585f1517a 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) @@ -351,6 +350,41 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, return InvalidOid; } + /* + * 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; + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup); - /* - * 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); - - /* - * 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; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 96b0aa735f..8daddbefd0 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.329 2010/01/15 22:36:34 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.330 2010/01/17 22:56:22 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1001,8 +1001,8 @@ standard_ProcessUtility(Node *parsetree, break; case T_CreateTrigStmt: - CreateTrigger((CreateTrigStmt *) parsetree, queryString, - InvalidOid, InvalidOid, NULL, true); + (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, + InvalidOid, InvalidOid, false); break; case T_DropPropertyStmt: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index d9cc1258f0..2eeb0bc515 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.318 2010/01/02 16:57:55 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.319 2010/01/17 22:56:22 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -519,7 +519,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) tgname = NameStr(trigrec->tgname); appendStringInfo(&buf, "CREATE %sTRIGGER %s", - trigrec->tgisconstraint ? "CONSTRAINT " : "", + OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "", quote_identifier(tgname)); appendStringInfoString(&buf, pretty ? "\n " : " "); @@ -577,7 +577,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) generate_relation_name(trigrec->tgrelid, NIL)); appendStringInfoString(&buf, pretty ? "\n " : " "); - if (trigrec->tgisconstraint) + if (OidIsValid(trigrec->tgconstraint)) { if (OidIsValid(trigrec->tgconstrrelid)) { @@ -1276,6 +1276,15 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, break; } + case CONSTRAINT_TRIGGER: + /* + * There isn't an ALTER TABLE syntax for creating a user-defined + * constraint trigger, but it seems better to print something + * than throw an error; if we throw error then this function + * couldn't safely be applied to all rows of pg_constraint. + */ + appendStringInfo(&buf, "TRIGGER"); + break; case CONSTRAINT_EXCLUSION: { Oid indexOid = conForm->conindid; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 6c9cb886b9..05fc7ad5a4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.566 2010/01/06 05:18:18 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.567 2010/01/17 22:56:22 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -4584,7 +4584,7 @@ getTriggers(TableInfo tblinfo[], int numTables) "tgenabled, tableoid, oid " "FROM pg_catalog.pg_trigger t " "WHERE tgrelid = '%u'::pg_catalog.oid " - "AND tgconstraint = 0", + "AND NOT tgisinternal", tbinfo->dobj.catId.oid); } else if (g_fout->remoteVersion >= 80300) diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4fdf864174..dca1f577c0 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -8,7 +8,7 @@ * * Copyright (c) 2000-2010, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.233 2010/01/02 16:57:59 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.234 2010/01/17 22:56:23 tgl Exp $ */ #include "postgres_fe.h" @@ -1849,7 +1849,7 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } - /* print triggers (but ignore RI and unique constraint triggers) */ + /* print triggers (but only user-defined triggers) */ if (tableinfo.hastriggers) { printfPQExpBuffer(&buf, @@ -1859,7 +1859,9 @@ describeOneTableDetails(const char *schemaname, "FROM pg_catalog.pg_trigger t\n" "WHERE t.tgrelid = '%s' AND ", oid); - if (pset.sversion >= 80300) + if (pset.sversion >= 80500) + appendPQExpBuffer(&buf, "NOT t.tgisinternal"); + else if (pset.sversion >= 80300) appendPQExpBuffer(&buf, "t.tgconstraint = 0"); else appendPQExpBuffer(&buf, diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b2c92860f0..1740b2b38c 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.573 2010/01/15 09:19:07 heikki Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.574 2010/01/17 22:56:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201001151 +#define CATALOG_VERSION_NO 201001171 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 6bbb6f7192..0d29875e52 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.115 2010/01/05 01:06:56 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.116 2010/01/17 22:56:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -220,8 +220,8 @@ DECLARE_UNIQUE_INDEX(pg_tablespace_spcname_index, 2698, on pg_tablespace using b #define TablespaceNameIndexId 2698 /* This following index is not used for a cache and is not unique */ -DECLARE_INDEX(pg_trigger_tgconstrname_index, 2699, on pg_trigger using btree(tgconstrname name_ops)); -#define TriggerConstrNameIndexId 2699 +DECLARE_INDEX(pg_trigger_tgconstraint_index, 2699, on pg_trigger using btree(tgconstraint oid_ops)); +#define TriggerConstraintIndexId 2699 DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using btree(tgrelid oid_ops, tgname name_ops)); #define TriggerRelidNameIndexId 2701 DECLARE_UNIQUE_INDEX(pg_trigger_oid_index, 2702, on pg_trigger using btree(oid oid_ops)); diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 4242b24c0e..1aada159db 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.36 2010/01/05 01:06:56 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.37 2010/01/17 22:56:23 tgl Exp $ * * NOTES * the genbki.pl script reads this file and generates .bki @@ -38,7 +38,7 @@ CATALOG(pg_constraint,2606) * Postgres practice, and partly because we don't want to have to obtain a * global lock to generate a globally unique name for a nameless * constraint. We associate a namespace with constraint names only for - * SQL92 compatibility. + * SQL-spec compatibility. */ NameData conname; /* name of this constraint */ Oid connamespace; /* OID of namespace containing constraint */ @@ -92,7 +92,8 @@ CATALOG(pg_constraint,2606) */ /* - * Columns of conrelid that the constraint applies to + * Columns of conrelid that the constraint applies to, if known + * (this is NULL for trigger constraints) */ int2 conkey[1]; @@ -177,6 +178,7 @@ typedef FormData_pg_constraint *Form_pg_constraint; #define CONSTRAINT_FOREIGN 'f' #define CONSTRAINT_PRIMARY 'p' #define CONSTRAINT_UNIQUE 'u' +#define CONSTRAINT_TRIGGER 't' #define CONSTRAINT_EXCLUSION 'x' /* diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h index 8e1a4bf12b..7c95475043 100644 --- a/src/include/catalog/pg_trigger.h +++ b/src/include/catalog/pg_trigger.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.38 2010/01/05 01:06:57 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.39 2010/01/17 22:56:23 tgl Exp $ * * NOTES * the genbki.pl script reads this file and generates .bki @@ -25,11 +25,10 @@ * pg_trigger definition. cpp turns this into * typedef struct FormData_pg_trigger * - * Note: when tgconstraint is nonzero, tgisconstraint must be true, and - * tgconstrname, tgconstrrelid, tgconstrindid, tgdeferrable, tginitdeferred - * are redundant with the referenced pg_constraint entry. The reason we keep - * these fields is that we support "stand-alone" constraint triggers with no - * corresponding pg_constraint entry. + * Note: when tgconstraint is nonzero, tgconstrrelid, tgconstrindid, + * tgdeferrable, and tginitdeferred are largely redundant with the referenced + * pg_constraint entry. However, it is possible for a non-deferrable trigger + * to be associated with a deferrable constraint. * ---------------- */ #define TriggerRelationId 2620 @@ -43,11 +42,10 @@ CATALOG(pg_trigger,2620) * ROW/STATEMENT; see below */ char tgenabled; /* trigger's firing configuration WRT * session_replication_role */ - bool tgisconstraint; /* trigger is a constraint trigger */ - NameData tgconstrname; /* constraint name */ + bool tgisinternal; /* trigger is system-generated */ Oid tgconstrrelid; /* constraint's FROM table, if any */ Oid tgconstrindid; /* constraint's supporting index, if any */ - Oid tgconstraint; /* owning pg_constraint entry, if any */ + Oid tgconstraint; /* associated pg_constraint entry, if any */ bool tgdeferrable; /* constraint trigger is deferrable */ bool tginitdeferred; /* constraint trigger is deferred initially */ int2 tgnargs; /* # of extra arguments in tgargs */ @@ -69,23 +67,22 @@ typedef FormData_pg_trigger *Form_pg_trigger; * compiler constants for pg_trigger * ---------------- */ -#define Natts_pg_trigger 16 +#define Natts_pg_trigger 15 #define Anum_pg_trigger_tgrelid 1 #define Anum_pg_trigger_tgname 2 #define Anum_pg_trigger_tgfoid 3 #define Anum_pg_trigger_tgtype 4 #define Anum_pg_trigger_tgenabled 5 -#define Anum_pg_trigger_tgisconstraint 6 -#define Anum_pg_trigger_tgconstrname 7 -#define Anum_pg_trigger_tgconstrrelid 8 -#define Anum_pg_trigger_tgconstrindid 9 -#define Anum_pg_trigger_tgconstraint 10 -#define Anum_pg_trigger_tgdeferrable 11 -#define Anum_pg_trigger_tginitdeferred 12 -#define Anum_pg_trigger_tgnargs 13 -#define Anum_pg_trigger_tgattr 14 -#define Anum_pg_trigger_tgargs 15 -#define Anum_pg_trigger_tgqual 16 +#define Anum_pg_trigger_tgisinternal 6 +#define Anum_pg_trigger_tgconstrrelid 7 +#define Anum_pg_trigger_tgconstrindid 8 +#define Anum_pg_trigger_tgconstraint 9 +#define Anum_pg_trigger_tgdeferrable 10 +#define Anum_pg_trigger_tginitdeferred 11 +#define Anum_pg_trigger_tgnargs 12 +#define Anum_pg_trigger_tgattr 13 +#define Anum_pg_trigger_tgargs 14 +#define Anum_pg_trigger_tgqual 15 /* Bits within tgtype */ #define TRIGGER_TYPE_ROW (1 << 0) diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index b5f44bb33e..db79eb4be7 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.79 2010/01/02 16:58:03 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.80 2010/01/17 22:56:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -105,8 +105,8 @@ extern PGDLLIMPORT int SessionReplicationRole; #define TRIGGER_DISABLED 'D' extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString, - Oid constraintOid, Oid indexOid, const char *prefix, - bool checkPermissions); + Oid constraintOid, Oid indexOid, + bool isInternal); extern void DropTrigger(Oid relid, const char *trigname, DropBehavior behavior, bool missing_ok); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index a03597c9f3..c752d071d5 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.424 2010/01/15 22:36:35 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.425 2010/01/17 22:56:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1593,9 +1593,8 @@ typedef struct CreateTrigStmt int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */ List *columns; /* column names, or NIL for all columns */ Node *whenClause; /* qual expression, or NULL if none */ - - /* The following are used for constraint triggers (RI and unique checks) */ bool isconstraint; /* This is a constraint trigger */ + /* The remaining fields are only used for constraint triggers */ bool deferrable; /* [NOT] DEFERRABLE */ bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ RangeVar *constrrel; /* opposite relation, if RI trigger */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 250a127ed3..344f37a687 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.119 2010/01/10 22:19:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.120 2010/01/17 22:56:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,7 +56,7 @@ typedef struct Trigger Oid tgfoid; int16 tgtype; char tgenabled; - bool tgisconstraint; + bool tgisinternal; Oid tgconstrrelid; Oid tgconstrindid; Oid tgconstraint;