1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-26 01:22:12 +03:00

Fix up foreign-key mechanism so that there is a sound semantic basis for the

equality checks it applies, instead of a random dependence on whatever
operators might be named "=".  The equality operators will now be selected
from the opfamily of the unique index that the FK constraint depends on to
enforce uniqueness of the referenced columns; therefore they are certain to be
consistent with that index's notion of equality.  Among other things this
should fix the problem noted awhile back that pg_dump may fail for foreign-key
constraints on user-defined types when the required operators aren't in the
search path.  This also means that the former warning condition about "foreign
key constraint will require costly sequential scans" is gone: if the
comparison condition isn't indexable then we'll reject the constraint
entirely. All per past discussions.

Along the way, make the RI triggers look into pg_constraint for their
information, instead of using pg_trigger.tgargs; and get rid of the always
error-prone fixed-size string buffers in ri_triggers.c in favor of building up
the RI queries in StringInfo buffers.

initdb forced due to columns added to pg_constraint and pg_trigger.
This commit is contained in:
Tom Lane
2007-02-14 01:58:58 +00:00
parent 65e2f55031
commit 7bddca3450
33 changed files with 1731 additions and 1665 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.30 2007/01/05 22:19:25 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.31 2007/02/14 01:58:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -146,7 +146,7 @@ RenameConversion(List *name, const char *newname)
errmsg("conversion \"%s\" does not exist",
NameListToString(name))));
tup = SearchSysCacheCopy(CONOID,
tup = SearchSysCacheCopy(CONVOID,
ObjectIdGetDatum(conversionOid),
0, 0, 0);
if (!HeapTupleIsValid(tup)) /* should not happen */
@ -236,7 +236,7 @@ AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId)
Assert(RelationGetRelid(rel) == ConversionRelationId);
tup = SearchSysCacheCopy(CONOID,
tup = SearchSysCacheCopy(CONVOID,
ObjectIdGetDatum(conversionOid),
0, 0, 0);
if (!HeapTupleIsValid(tup)) /* should not happen */

View File

@ -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.153 2007/01/05 22:19:26 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.154 2007/02/14 01:58:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -327,8 +327,8 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
if (instr->ntuples == 0)
continue;
if (trig->tgisconstraint &&
(conname = GetConstraintNameForTrigger(trig->tgoid)) != NULL)
if (OidIsValid(trig->tgconstraint) &&
(conname = get_constraint_name(trig->tgconstraint)) != NULL)
{
appendStringInfo(&buf, "Trigger for constraint %s",
conname);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.213 2007/02/02 00:07:02 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.214 2007/02/14 01:58:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -28,6 +28,7 @@
#include "catalog/pg_depend.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/toasting.h"
@ -139,6 +140,7 @@ typedef struct NewConstraint
char *name; /* Constraint name, or NULL if none */
ConstrType contype; /* CHECK or FOREIGN */
Oid refrelid; /* PK rel, if FOREIGN */
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
Node *qual; /* Check expr or FkConstraint struct */
List *qualstate; /* Execution state for CHECK */
} NewConstraint;
@ -186,10 +188,9 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
int numattrs, int16 *attnums,
Oid *opclasses);
static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
Relation rel, Relation pkrel);
Relation rel, Relation pkrel, Oid constraintOid);
static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
Oid constrOid);
static char *fkMatchTypeToString(char match_type);
Oid constraintOid);
static void ATController(Relation rel, List *cmds, bool recurse);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing);
@ -256,11 +257,6 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
static void ATExecAddInherit(Relation rel, RangeVar *parent);
static void ATExecDropInherit(Relation rel, RangeVar *parent);
static void copy_relation_data(Relation rel, SMgrRelation dst);
static void update_ri_trigger_args(Oid relid,
const char *oldname,
const char *newname,
bool fk_scan,
bool update_relname);
/* ----------------------------------------------------------------
@ -1615,21 +1611,6 @@ renameatt(Oid myrelid,
heap_close(attrelation, RowExclusiveLock);
/*
* Update att name in any RI triggers associated with the relation.
*/
if (targetrelation->rd_rel->reltriggers > 0)
{
/* update tgargs column reference where att is primary key */
update_ri_trigger_args(RelationGetRelid(targetrelation),
oldattname, newattname,
false, false);
/* update tgargs column reference where att is foreign key */
update_ri_trigger_args(RelationGetRelid(targetrelation),
oldattname, newattname,
true, false);
}
relation_close(targetrelation, NoLock); /* close rel but keep lock */
}
@ -1708,226 +1689,12 @@ renamerel(Oid myrelid, const char *newrelname)
if (relkind != RELKIND_INDEX)
TypeRename(oldrelname, namespaceId, newrelname);
/*
* Update rel name in any RI triggers associated with the relation.
*/
if (relhastriggers)
{
/* update tgargs where relname is primary key */
update_ri_trigger_args(myrelid,
oldrelname,
newrelname,
false, true);
/* update tgargs where relname is foreign key */
update_ri_trigger_args(myrelid,
oldrelname,
newrelname,
true, true);
}
/*
* Close rel, but keep exclusive lock!
*/
relation_close(targetrelation, NoLock);
}
/*
* Scan pg_trigger for RI triggers that are on the specified relation
* (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
* is true). Update RI trigger args fields matching oldname to contain
* newname instead. If update_relname is true, examine the relname
* fields; otherwise examine the attname fields.
*/
static void
update_ri_trigger_args(Oid relid,
const char *oldname,
const char *newname,
bool fk_scan,
bool update_relname)
{
Relation tgrel;
ScanKeyData skey[1];
SysScanDesc trigscan;
HeapTuple tuple;
Datum values[Natts_pg_trigger];
char nulls[Natts_pg_trigger];
char replaces[Natts_pg_trigger];
tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
if (fk_scan)
{
ScanKeyInit(&skey[0],
Anum_pg_trigger_tgconstrrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
trigscan = systable_beginscan(tgrel, TriggerConstrRelidIndexId,
true, SnapshotNow,
1, skey);
}
else
{
ScanKeyInit(&skey[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
trigscan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
true, SnapshotNow,
1, skey);
}
while ((tuple = systable_getnext(trigscan)) != NULL)
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
bytea *val;
bytea *newtgargs;
bool isnull;
int tg_type;
bool examine_pk;
bool changed;
int tgnargs;
int i;
int newlen;
const char *arga[RI_MAX_ARGUMENTS];
const char *argp;
tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid);
if (tg_type == RI_TRIGGER_NONE)
{
/* Not an RI trigger, forget it */
continue;
}
/*
* It is an RI trigger, so parse the tgargs bytea.
*
* NB: we assume the field will never be compressed or moved out of
* line; so does trigger.c ...
*/
tgnargs = pg_trigger->tgnargs;
val = DatumGetByteaP(fastgetattr(tuple,
Anum_pg_trigger_tgargs,
tgrel->rd_att, &isnull));
if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
tgnargs > RI_MAX_ARGUMENTS)
{
/* This probably shouldn't happen, but ignore busted triggers */
continue;
}
argp = (const char *) VARDATA(val);
for (i = 0; i < tgnargs; i++)
{
arga[i] = argp;
argp += strlen(argp) + 1;
}
/*
* Figure out which item(s) to look at. If the trigger is primary-key
* type and attached to my rel, I should look at the PK fields; if it
* is foreign-key type and attached to my rel, I should look at the FK
* fields. But the opposite rule holds when examining triggers found
* by tgconstrrel search.
*/
examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
changed = false;
if (update_relname)
{
/* Change the relname if needed */
i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
if (strcmp(arga[i], oldname) == 0)
{
arga[i] = newname;
changed = true;
}
}
else
{
/* Change attname(s) if needed */
i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
for (; i < tgnargs; i += 2)
{
if (strcmp(arga[i], oldname) == 0)
{
arga[i] = newname;
changed = true;
}
}
}
if (!changed)
{
/* Don't need to update this tuple */
continue;
}
/*
* Construct modified tgargs bytea.
*/
newlen = VARHDRSZ;
for (i = 0; i < tgnargs; i++)
newlen += strlen(arga[i]) + 1;
newtgargs = (bytea *) palloc(newlen);
VARATT_SIZEP(newtgargs) = newlen;
newlen = VARHDRSZ;
for (i = 0; i < tgnargs; i++)
{
strcpy(((char *) newtgargs) + newlen, arga[i]);
newlen += strlen(arga[i]) + 1;
}
/*
* Build modified tuple.
*/
for (i = 0; i < Natts_pg_trigger; i++)
{
values[i] = (Datum) 0;
replaces[i] = ' ';
nulls[i] = ' ';
}
values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
replaces[Anum_pg_trigger_tgargs - 1] = 'r';
tuple = heap_modifytuple(tuple, RelationGetDescr(tgrel), values, nulls, replaces);
/*
* Update pg_trigger and its indexes
*/
simple_heap_update(tgrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(tgrel, tuple);
/*
* Invalidate trigger's relation's relcache entry so that other
* backends (and this one too!) are sent SI message to make them
* rebuild relcache entries. (Ideally this should happen
* automatically...)
*
* We can skip this for triggers on relid itself, since that relcache
* flush will happen anyway due to the table or column rename. We
* just need to catch the far ends of RI relationships.
*/
pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
if (pg_trigger->tgrelid != relid)
CacheInvalidateRelcacheByRelid(pg_trigger->tgrelid);
/* free up our scratch memory */
pfree(newtgargs);
heap_freetuple(tuple);
}
systable_endscan(trigscan);
heap_close(tgrel, RowExclusiveLock);
/*
* Increment cmd counter to make updates visible; this is needed in case
* the same tuple has to be updated again by next pass (can happen in case
* of a self-referential FK relationship).
*/
CommandCounterIncrement();
}
/*
* AlterTable
* Execute ALTER TABLE, which can be a list of subcommands
@ -2552,7 +2319,8 @@ ATRewriteTables(List **wqueue)
refrel = heap_open(con->refrelid, RowShareLock);
validateForeignKeyConstraint(fkconstraint, rel, refrel);
validateForeignKeyConstraint(fkconstraint, rel, refrel,
con->conid);
heap_close(refrel, NoLock);
}
@ -4061,6 +3829,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
Oid pfeqoperators[INDEX_MAX_KEYS];
Oid ppeqoperators[INDEX_MAX_KEYS];
Oid ffeqoperators[INDEX_MAX_KEYS];
int i;
int numfks,
numpks;
@ -4138,6 +3909,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
numfks = transformColumnNameList(RelationGetRelid(rel),
fkconstraint->fk_attrs,
@ -4166,7 +3940,15 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
opclasses);
}
/* Be sure referencing and referenced column types are comparable */
/*
* Look up the equality operators to use in the constraint.
*
* Note that we look for operator(s) with the PK type on the left; when
* the types are different this is the right choice because the PK index
* will need operators with the indexkey on the left. Also, we take the
* PK type as being the declared input type of the opclass, which might be
* binary-compatible but not identical to the PK column type.
*/
if (numfks != numpks)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
@ -4174,24 +3956,71 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
for (i = 0; i < numpks; i++)
{
/*
* pktypoid[i] is the primary key table's i'th key's type fktypoid[i]
* is the foreign key table's i'th key's type
*
* Note that we look for an operator with the PK type on the left;
* when the types are different this is critical because the PK index
* will need operators with the indexkey on the left. (Ordinarily both
* commutator operators will exist if either does, but we won't get
* the right answer from the test below on opclass membership unless
* we select the proper operator.)
*/
Operator o = oper(NULL, list_make1(makeString("=")),
pktypoid[i], fktypoid[i],
true, -1);
HeapTuple cla_ht;
Form_pg_opclass cla_tup;
Oid amid;
Oid opfamily;
Oid pktype;
Oid fktype;
Oid pfeqop;
Oid ppeqop;
Oid ffeqop;
int16 eqstrategy;
if (o == NULL)
/* We need several fields out of the pg_opclass entry */
cla_ht = SearchSysCache(CLAOID,
ObjectIdGetDatum(opclasses[i]),
0, 0, 0);
if (!HeapTupleIsValid(cla_ht))
elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
amid = cla_tup->opcmethod;
opfamily = cla_tup->opcfamily;
pktype = cla_tup->opcintype;
ReleaseSysCache(cla_ht);
/*
* Check it's a btree; currently this can never fail since no other
* index AMs support unique indexes. If we ever did have other
* types of unique indexes, we'd need a way to determine which
* operator strategy number is equality. (Is it reasonable to
* insist that every such index AM use btree's number for equality?)
*/
if (amid != BTREE_AM_OID)
elog(ERROR, "only b-tree indexes are supported for foreign keys");
eqstrategy = BTEqualStrategyNumber;
/*
* There had better be a PK = PK operator for the index.
*/
ppeqop = get_opfamily_member(opfamily, pktype, pktype, eqstrategy);
if (!OidIsValid(ppeqop))
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
eqstrategy, pktype, pktype, opfamily);
/*
* Are there equality operators that take exactly the FK type?
* Assume we should look through any domain here.
*/
fktype = getBaseType(fktypoid[i]);
pfeqop = get_opfamily_member(opfamily, pktype, fktype, eqstrategy);
ffeqop = get_opfamily_member(opfamily, fktype, fktype, eqstrategy);
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
{
/*
* Otherwise, look for an implicit cast from the FK type to
* the PK type, and if found, use the PK type's equality operator.
*/
if (can_coerce_type(1, &fktype, &pktype, COERCION_IMPLICIT))
pfeqop = ffeqop = ppeqop;
}
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("foreign key constraint \"%s\" "
"cannot be implemented",
fkconstraint->constr_name),
@ -4202,41 +4031,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
format_type_be(fktypoid[i]),
format_type_be(pktypoid[i]))));
/*
* Check that the found operator is compatible with the PK index, and
* generate a warning if not, since otherwise costly seqscans will be
* incurred to check FK validity.
*/
if (!op_in_opfamily(oprid(o), get_opclass_family(opclasses[i])))
ereport(WARNING,
(errmsg("foreign key constraint \"%s\" "
"will require costly sequential scans",
fkconstraint->constr_name),
errdetail("Key columns \"%s\" and \"%s\" "
"are of different types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktypoid[i]),
format_type_be(pktypoid[i]))));
ReleaseSysCache(o);
}
/*
* Tell Phase 3 to check that the constraint is satisfied by existing rows
* (we can skip this during table creation).
*/
if (!fkconstraint->skip_validation)
{
NewConstraint *newcon;
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
newcon->name = fkconstraint->constr_name;
newcon->contype = CONSTR_FOREIGN;
newcon->refrelid = RelationGetRelid(pkrel);
newcon->qual = (Node *) fkconstraint;
tab->constraints = lappend(tab->constraints, newcon);
pfeqoperators[i] = pfeqop;
ppeqoperators[i] = ppeqop;
ffeqoperators[i] = ffeqop;
}
/*
@ -4254,6 +4051,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* constraint */
RelationGetRelid(pkrel),
pkattnum,
pfeqoperators,
ppeqoperators,
ffeqoperators,
numpks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
@ -4268,6 +4068,24 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
createForeignKeyTriggers(rel, fkconstraint, constrOid);
/*
* Tell Phase 3 to check that the constraint is satisfied by existing rows
* (we can skip this during table creation).
*/
if (!fkconstraint->skip_validation)
{
NewConstraint *newcon;
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
newcon->name = fkconstraint->constr_name;
newcon->contype = CONSTR_FOREIGN;
newcon->refrelid = RelationGetRelid(pkrel);
newcon->conid = constrOid;
newcon->qual = (Node *) fkconstraint;
tab->constraints = lappend(tab->constraints, newcon);
}
/*
* Close pk table, but keep lock until we've committed.
*/
@ -4526,25 +4344,15 @@ transformFkeyCheckAttrs(Relation pkrel,
static void
validateForeignKeyConstraint(FkConstraint *fkconstraint,
Relation rel,
Relation pkrel)
Relation pkrel,
Oid constraintOid)
{
HeapScanDesc scan;
HeapTuple tuple;
Trigger trig;
ListCell *list;
int count;
/*
* See if we can do it with a single LEFT JOIN query. A FALSE result
* indicates we must proceed with the fire-the-trigger method.
*/
if (RI_Initial_Check(fkconstraint, rel, pkrel))
return;
/*
* Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
* if that tuple had just been inserted. If any of those fail, it should
* ereport(ERROR) and that's that.
* Build a trigger call structure; we'll need it either way.
*/
MemSet(&trig, 0, sizeof(trig));
trig.tgoid = InvalidOid;
@ -4552,35 +4360,23 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
trig.tgenabled = TRUE;
trig.tgisconstraint = TRUE;
trig.tgconstrrelid = RelationGetRelid(pkrel);
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
/* we needn't fill in tgargs */
trig.tgargs = (char **) palloc(sizeof(char *) *
(4 + list_length(fkconstraint->fk_attrs)
+ list_length(fkconstraint->pk_attrs)));
trig.tgargs[0] = trig.tgname;
trig.tgargs[1] = RelationGetRelationName(rel);
trig.tgargs[2] = RelationGetRelationName(pkrel);
trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
count = 4;
foreach(list, fkconstraint->fk_attrs)
{
char *fk_at = strVal(lfirst(list));
trig.tgargs[count] = fk_at;
count += 2;
}
count = 5;
foreach(list, fkconstraint->pk_attrs)
{
char *pk_at = strVal(lfirst(list));
trig.tgargs[count] = pk_at;
count += 2;
}
trig.tgnargs = count - 1;
/*
* See if we can do it with a single LEFT JOIN query. A FALSE result
* indicates we must proceed with the fire-the-trigger method.
*/
if (RI_Initial_Check(&trig, rel, pkrel))
return;
/*
* Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
* if that tuple had just been inserted. If any of those fail, it should
* ereport(ERROR) and that's that.
*/
scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
@ -4613,18 +4409,13 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
}
heap_endscan(scan);
pfree(trig.tgargs);
}
static void
CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
ObjectAddress *constrobj, ObjectAddress *trigobj,
bool on_insert)
Oid constraintOid, bool on_insert)
{
CreateTrigStmt *fk_trigger;
ListCell *fk_attr;
ListCell *pk_attr;
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = fkconstraint->constr_name;
@ -4649,32 +4440,9 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
fk_trigger->constrrel = fkconstraint->pktable;
fk_trigger->args = NIL;
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->constr_name));
fk_trigger->args = lappend(fk_trigger->args,
makeString(myRel->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->pktable->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
if (list_length(fkconstraint->fk_attrs) != list_length(fkconstraint->pk_attrs))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("number of referencing and referenced columns for foreign key disagree")));
forboth(fk_attr, fkconstraint->fk_attrs,
pk_attr, fkconstraint->pk_attrs)
{
fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
}
trigobj->objectId = CreateTrigger(fk_trigger, true);
/* Register dependency from trigger to constraint */
recordDependencyOn(trigobj, constrobj, DEPENDENCY_INTERNAL);
(void) CreateTrigger(fk_trigger, constraintOid);
/* Make changes-so-far visible */
CommandCounterIncrement();
@ -4685,14 +4453,10 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
*/
static void
createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
Oid constrOid)
Oid constraintOid)
{
RangeVar *myRel;
CreateTrigStmt *fk_trigger;
ListCell *fk_attr;
ListCell *pk_attr;
ObjectAddress trigobj,
constrobj;
/*
* Reconstruct a RangeVar for my relation (not passed in, unfortunately).
@ -4700,15 +4464,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
pstrdup(RelationGetRelationName(rel)));
/*
* Preset objectAddress fields
*/
constrobj.classId = ConstraintRelationId;
constrobj.objectId = constrOid;
constrobj.objectSubId = 0;
trigobj.classId = TriggerRelationId;
trigobj.objectSubId = 0;
/* Make changes-so-far visible */
CommandCounterIncrement();
@ -4716,8 +4471,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the CHECK
* action for both INSERTs and UPDATEs on the referencing table.
*/
CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, true);
CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, false);
CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, true);
CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, false);
/*
* Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
@ -4765,27 +4520,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
(int) fkconstraint->fk_del_action);
break;
}
fk_trigger->args = NIL;
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->constr_name));
fk_trigger->args = lappend(fk_trigger->args,
makeString(myRel->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->pktable->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
forboth(fk_attr, fkconstraint->fk_attrs,
pk_attr, fkconstraint->pk_attrs)
{
fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
}
trigobj.objectId = CreateTrigger(fk_trigger, true);
/* Register dependency from trigger to constraint */
recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
(void) CreateTrigger(fk_trigger, constraintOid);
/* Make changes-so-far visible */
CommandCounterIncrement();
@ -4835,49 +4572,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
(int) fkconstraint->fk_upd_action);
break;
}
fk_trigger->args = NIL;
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->constr_name));
fk_trigger->args = lappend(fk_trigger->args,
makeString(myRel->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkconstraint->pktable->relname));
fk_trigger->args = lappend(fk_trigger->args,
makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
forboth(fk_attr, fkconstraint->fk_attrs,
pk_attr, fkconstraint->pk_attrs)
{
fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
}
trigobj.objectId = CreateTrigger(fk_trigger, true);
/* Register dependency from trigger to constraint */
recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
}
/*
* fkMatchTypeToString -
* convert FKCONSTR_MATCH_xxx code to string to use in trigger args
*/
static char *
fkMatchTypeToString(char match_type)
{
switch (match_type)
{
case FKCONSTR_MATCH_FULL:
return pstrdup("FULL");
case FKCONSTR_MATCH_PARTIAL:
return pstrdup("PARTIAL");
case FKCONSTR_MATCH_UNSPECIFIED:
return pstrdup("UNSPECIFIED");
default:
elog(ERROR, "unrecognized match type: %d",
(int) match_type);
}
return NULL; /* can't get here */
(void) CreateTrigger(fk_trigger, constraintOid);
}
/*

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.212 2007/01/25 04:17:46 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.213 2007/02/14 01:58:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -19,6 +19,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@ -57,13 +58,12 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
/*
* Create a trigger. Returns the OID of the created trigger.
*
* forConstraint, if true, says that this trigger is being created to
* implement a constraint. The caller will then be expected to make
* a pg_depend entry linking the trigger to that constraint (and thereby
* to the owning relation(s)).
* constraintOid, if nonzero, says that this trigger is being created to
* implement that constraint. A suitable pg_depend entry will be made
* to link the trigger to that constraint.
*/
Oid
CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
{
int16 tgtype;
int2vector *tgattr;
@ -91,51 +91,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
rel = heap_openrv(stmt->relation, AccessExclusiveLock);
if (stmt->constrrel != NULL)
constrrelid = RangeVarGetRelid(stmt->constrrel, false);
else if (stmt->isconstraint)
{
/*
* If this trigger is a constraint (and a foreign key one) then we
* really need a constrrelid. Since we don't have one, we'll try to
* generate one from the argument information.
*
* This is really just a workaround for a long-ago pg_dump bug that
* omitted the FROM clause in dumped CREATE CONSTRAINT TRIGGER
* commands. We don't want to bomb out completely here if we can't
* determine the correct relation, because that would prevent loading
* the dump file. Instead, NOTICE here and ERROR in the trigger.
*/
bool needconstrrelid = false;
void *elem = NULL;
if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check_", 14) == 0)
{
/* A trigger on FK table. */
needconstrrelid = true;
if (list_length(stmt->args) > RI_PK_RELNAME_ARGNO)
elem = list_nth(stmt->args, RI_PK_RELNAME_ARGNO);
}
else if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_", 8) == 0)
{
/* A trigger on PK table. */
needconstrrelid = true;
if (list_length(stmt->args) > RI_FK_RELNAME_ARGNO)
elem = list_nth(stmt->args, RI_FK_RELNAME_ARGNO);
}
if (elem != NULL)
{
RangeVar *rel = makeRangeVar(NULL, strVal(elem));
constrrelid = RangeVarGetRelid(rel, true);
}
if (needconstrrelid && constrrelid == InvalidOid)
ereport(NOTICE,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("could not determine referenced table for constraint \"%s\"",
stmt->trigname)));
}
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@ -152,15 +107,17 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
if (stmt->isconstraint)
{
/* foreign key constraint trigger */
/* constraint trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (constrrelid != InvalidOid)
if (stmt->constrrel != NULL)
{
constrrelid = RangeVarGetRelid(stmt->constrrel, false);
aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
@ -170,7 +127,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
}
else
{
/* real trigger */
/* regular trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
@ -187,23 +144,31 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
trigoid = GetNewOid(tgrel);
/*
* If trigger is an RI constraint, use specified trigger name as
* constraint name and build a unique trigger name instead. This is mainly
* for backwards compatibility with CREATE CONSTRAINT TRIGGER commands.
* If trigger is for an RI constraint, the passed-in name is the
* constraint name; save that and build a unique trigger name to avoid
* collisions with user-selected trigger names.
*/
if (stmt->isconstraint)
if (OidIsValid(constraintOid))
{
snprintf(constrtrigname, sizeof(constrtrigname),
"RI_ConstraintTrigger_%u", trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
else if (stmt->isconstraint)
{
/* constraint trigger: trigger name is also constraint name */
trigname = stmt->trigname;
constrname = stmt->trigname;
}
else
{
/* regular trigger: use empty constraint name */
trigname = stmt->trigname;
constrname = "";
}
/* Compute tgtype */
TRIGGER_CLEAR_TYPE(tgtype);
if (stmt->before)
TRIGGER_SETT_BEFORE(tgtype);
@ -298,7 +263,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
/*
* Build the new pg_trigger tuple.
*/
MemSet(nulls, ' ', Natts_pg_trigger * sizeof(char));
memset(nulls, ' ', Natts_pg_trigger * sizeof(char));
values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
@ -310,6 +275,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
values[Anum_pg_trigger_tgconstrname - 1] = DirectFunctionCall1(namein,
CStringGetDatum(constrname));
values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
@ -372,10 +338,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
CatalogUpdateIndexes(tgrel, tuple);
myself.classId = TriggerRelationId;
myself.objectId = trigoid;
myself.objectSubId = 0;
heap_freetuple(tuple);
heap_close(tgrel, RowExclusiveLock);
@ -412,20 +374,36 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
/*
* Record dependencies for trigger. Always place a normal dependency on
* the function. If we are doing this in response to an explicit CREATE
* TRIGGER command, also 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.) If the trigger is being made for a
* constraint, we can skip the relation links; the dependency on the
* constraint will indirectly depend on the relations.
* the function.
*/
myself.classId = TriggerRelationId;
myself.objectId = trigoid;
myself.objectSubId = 0;
referenced.classId = ProcedureRelationId;
referenced.objectId = funcoid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
if (!forConstraint)
if (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.
*/
referenced.classId = ConstraintRelationId;
referenced.objectId = constraintOid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
}
else
{
/*
* Regular 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.)
*/
referenced.classId = RelationRelationId;
referenced.objectId = RelationGetRelid(rel);
referenced.objectSubId = 0;
@ -773,7 +751,7 @@ EnableDisableTrigger(Relation rel, const char *tgname,
{
Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);
if (oldtrig->tgisconstraint)
if (OidIsValid(oldtrig->tgconstraint))
{
/* system trigger ... ok to process? */
if (skip_system)
@ -886,6 +864,7 @@ RelationBuildTriggers(Relation relation)
build->tgenabled = pg_trigger->tgenabled;
build->tgisconstraint = pg_trigger->tgisconstraint;
build->tgconstrrelid = pg_trigger->tgconstrrelid;
build->tgconstraint = pg_trigger->tgconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
build->tgnargs = pg_trigger->tgnargs;
@ -1220,6 +1199,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tgconstrrelid != trig2->tgconstrrelid)
return false;
if (trig1->tgconstraint != trig2->tgconstraint)
return false;
if (trig1->tgdeferrable != trig2->tgdeferrable)
return false;
if (trig1->tginitdeferred != trig2->tginitdeferred)

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.99 2007/01/05 22:19:26 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.100 2007/02/14 01:58:57 tgl Exp $
*
* DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the
@ -1966,6 +1966,9 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
domainOid, /* domain constraint */
InvalidOid, /* Foreign key fields */
NULL,
NULL,
NULL,
NULL,
0,
' ',
' ',