mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +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:
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user