*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
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;
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);
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);
/* ----------------------------------------------------------------
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 */
}
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
*
refrel = heap_open(con->refrelid, RowShareLock);
- validateForeignKeyConstraint(fkconstraint, rel, refrel);
+ validateForeignKeyConstraint(fkconstraint, rel, refrel,
+ con->conid);
heap_close(refrel, NoLock);
}
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;
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,
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),
for (i = 0; i < numpks; i++)
{
+ HeapTuple cla_ht;
+ Form_pg_opclass cla_tup;
+ Oid amid;
+ Oid opfamily;
+ Oid pktype;
+ Oid fktype;
+ Oid pfeqop;
+ Oid ppeqop;
+ Oid ffeqop;
+ int16 eqstrategy;
+
+ /* 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);
+
/*
- * 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.)
+ * 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.
*/
- Operator o = oper(NULL, list_make1(makeString("=")),
- pktypoid[i], fktypoid[i],
- true, -1);
+ fktype = getBaseType(fktypoid[i]);
+
+ pfeqop = get_opfamily_member(opfamily, pktype, fktype, eqstrategy);
+ ffeqop = get_opfamily_member(opfamily, fktype, fktype, eqstrategy);
- if (o == NULL)
+ 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),
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;
}
/*
* constraint */
RelationGetRelid(pkrel),
pkattnum,
+ pfeqoperators,
+ ppeqoperators,
+ ffeqoperators,
numpks,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
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.
*/
heap_close(pkrel, NoLock);
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;
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)
}
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;
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();
*/
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).
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();
* 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
(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();
(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);
}
/*
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.90 2007/01/05 22:19:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.91 2007/02/14 01:58:57 tgl Exp $
*
* ----------
*/
#include "postgres.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_operator.h"
#include "commands/trigger.h"
#include "executor/spi_priv.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
+#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
-#include "utils/typcache.h"
-#include "miscadmin.h"
+#include "utils/memutils.h"
/* ----------
* ----------
*/
-#define RI_INIT_QUERYHASHSIZE 128
+#define RI_MAX_NUMKEYS INDEX_MAX_KEYS
-#define RI_MATCH_TYPE_UNSPECIFIED 0
-#define RI_MATCH_TYPE_FULL 1
-#define RI_MATCH_TYPE_PARTIAL 2
+#define RI_INIT_QUERYHASHSIZE 128
#define RI_KEYS_ALL_NULL 0
#define RI_KEYS_SOME_NULL 1
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
+#define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum))
+#define RIAttType(rel, attnum) SPI_gettypeid(RelationGetDescr(rel), attnum)
+
#define RI_TRIGTYPE_INSERT 1
#define RI_TRIGTYPE_UPDATE 2
#define RI_TRIGTYPE_INUP 3
#define RI_TRIGTYPE_DELETE 4
+#define RI_KEYPAIR_FK_IDX 0
+#define RI_KEYPAIR_PK_IDX 1
+
+
+/* ----------
+ * RI_ConstraintInfo
+ *
+ * Information extracted from an FK pg_constraint entry.
+ * ----------
+ */
+typedef struct RI_ConstraintInfo
+{
+ Oid constraint_id; /* OID of pg_constraint entry */
+ NameData conname; /* name of the FK constraint */
+ Oid pk_relid; /* referenced relation */
+ Oid fk_relid; /* referencing relation */
+ char confupdtype; /* foreign key's ON UPDATE action */
+ char confdeltype; /* foreign key's ON DELETE action */
+ char confmatchtype; /* foreign key's match type */
+ int nkeys; /* number of key columns */
+ int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
+ int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
+ Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
+ Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
+ Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
+} RI_ConstraintInfo;
+
/* ----------
* RI_QueryKey
*
- * The key identifying a prepared SPI plan in our private hashtable
+ * The key identifying a prepared SPI plan in our query hashtable
* ----------
*/
typedef struct RI_QueryKey
{
- int32 constr_type;
+ char constr_type;
Oid constr_id;
int32 constr_queryno;
Oid fk_relid;
/* ----------
+ * RI_CompareKey
+ *
+ * The key identifying an entry showing how to compare two values
+ * ----------
+ */
+typedef struct RI_CompareKey
+{
+ Oid eq_opr; /* the equality operator to apply */
+ Oid typeid; /* the data type to apply it to */
+} RI_CompareKey;
+
+
+/* ----------
+ * RI_CompareHashEntry
+ * ----------
+ */
+typedef struct RI_CompareHashEntry
+{
+ RI_CompareKey key;
+ bool valid; /* successfully initialized? */
+ FmgrInfo eq_opr_finfo; /* call info for equality fn */
+ FmgrInfo cast_func_finfo; /* in case we must coerce input */
+} RI_CompareHashEntry;
+
+
+/* ----------
* Local data
* ----------
*/
static HTAB *ri_query_cache = NULL;
+static HTAB *ri_compare_cache = NULL;
/* ----------
*/
static void quoteOneName(char *buffer, const char *name);
static void quoteRelationName(char *buffer, Relation rel);
-static int ri_DetermineMatchType(char *str);
+static void ri_GenerateQual(StringInfo buf,
+ const char *sep,
+ const char *leftop, Oid leftoptype,
+ Oid opoid,
+ const char *rightop, Oid rightoptype);
static int ri_NullCheck(Relation rel, HeapTuple tup,
RI_QueryKey *key, int pairidx);
-static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id,
- int32 constr_queryno,
- Relation fk_rel, Relation pk_rel,
- int argc, char **argv);
-static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id,
- int32 constr_queryno,
- Relation pk_rel,
- int argc, char **argv);
+static void ri_BuildQueryKeyFull(RI_QueryKey *key,
+ const RI_ConstraintInfo *riinfo,
+ int32 constr_queryno);
+static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key,
+ const RI_ConstraintInfo *riinfo,
+ int32 constr_queryno);
static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx);
+ const RI_ConstraintInfo *riinfo, bool rel_is_pk);
static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx);
-static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup,
- HeapTuple newtup, RI_QueryKey *key, int pairidx);
-static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);
+ const RI_ConstraintInfo *riinfo, bool rel_is_pk);
+static bool ri_OneKeyEqual(Relation rel, int column,
+ HeapTuple oldtup, HeapTuple newtup,
+ const RI_ConstraintInfo *riinfo, bool rel_is_pk);
+static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
+ Datum oldvalue, Datum newvalue);
static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
HeapTuple old_row,
- Oid tgoid, int match_type,
- int tgnargs, char **tgargs);
+ const RI_ConstraintInfo *riinfo);
static void ri_InitHashTables(void);
static void *ri_FetchPreparedPlan(RI_QueryKey *key);
static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
int tgkind);
+static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
+ Trigger *trigger, Relation trig_rel, bool rel_is_pk);
static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
bool cache_plan);
RI_FKey_check(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
RI_QueryKey qkey;
void *qplan;
int i;
- int match_type;
/*
* Check that this is a valid trigger call on the right time and event.
/*
* Get arguments.
*/
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, false);
+
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
old_row = trigdata->tg_trigtuple;
* SELECT FOR SHARE will get on it.
*/
fk_rel = trigdata->tg_relation;
- pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+ pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
/* ----------
* SQL3 11.9 <referential constraint definition>
* future enhancements.
* ----------
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
{
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CHECK_LOOKUPPK_NOCOLS,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ ri_BuildQueryKeyFull(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK_NOCOLS);
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed");
* ----------
*/
quoteRelationName(pkrelname, pk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x FOR SHARE OF x",
+ snprintf(querystr, sizeof(querystr),
+ "SELECT 1 FROM ONLY %s x FOR SHARE OF x",
pkrelname);
/* Prepare and save the plan */
NULL, NULL,
false,
SPI_OK_SELECT,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
-
}
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
- if (match_type == RI_MATCH_TYPE_PARTIAL)
+ if (riinfo.confmatchtype == FKCONSTR_MATCH_PARTIAL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CHECK_LOOKUPPK, fk_rel, pk_rel,
- tgnargs, tgargs);
+ ri_BuildQueryKeyFull(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK);
switch (ri_NullCheck(fk_rel, new_row, &qkey, RI_KEYPAIR_FK_IDX))
{
* This is the only case that differs between the three kinds of
* MATCH.
*/
- switch (match_type)
+ switch (riinfo.confmatchtype)
{
- case RI_MATCH_TYPE_FULL:
+ case FKCONSTR_MATCH_FULL:
/*
* Not allowed - MATCH FULL says either all or none of the
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
- tgargs[RI_CONSTRAINT_NAME_ARGNO]),
+ NameStr(riinfo.conname)),
errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
- case RI_MATCH_TYPE_UNSPECIFIED:
+ case FKCONSTR_MATCH_UNSPECIFIED:
/*
* MATCH <unspecified> - if ANY column is null, we have a
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
/*
* MATCH PARTIAL - all non-null columns must match. (not
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...]
+ * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
* The type id's for the $ parameters are those of the
- * corresponding FK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding FK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(pkrelname, pk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname);
+ appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(pk_rel, riinfo.pk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ attname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ paramname, fk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(fk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_FK_IDX]);
+ queryoids[i] = fk_type;
}
- strcat(querystr, " FOR SHARE OF x");
+ appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
NULL, new_row,
false,
SPI_OK_SELECT,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/* ----------
* ri_Check_Pk_Match
*
- * Check for matching value of old pk row in current state for
+ * Check for matching value of old pk row in current state for
* noaction triggers. Returns false if no row was found and a fk row
* could potentially be referencing this row, true otherwise.
* ----------
static bool
ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
HeapTuple old_row,
- Oid tgoid, int match_type,
- int tgnargs, char **tgargs)
+ const RI_ConstraintInfo *riinfo)
{
void *qplan;
RI_QueryKey qkey;
int i;
bool result;
- ri_BuildQueryKeyPkCheck(&qkey, tgoid,
- RI_PLAN_CHECK_LOOKUPPK, pk_rel,
- tgnargs, tgargs);
+ ri_BuildQueryKeyPkCheck(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
* This is the only case that differs between the three kinds of
* MATCH.
*/
- switch (match_type)
+ switch (riinfo->confmatchtype)
{
- case RI_MATCH_TYPE_FULL:
- case RI_MATCH_TYPE_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ case FKCONSTR_MATCH_UNSPECIFIED:
/*
* MATCH <unspecified>/FULL - if ANY column is null, we
*/
return true;
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
/*
* MATCH PARTIAL - all non-null columns must match. (not
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...]
+ * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
* The type id's for the $ parameters are those of the
- * corresponding FK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * PK attributes themselves.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(pkrelname, pk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname);
+ appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo->nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(pk_rel, riinfo->pk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ attname, pk_type,
+ riinfo->pp_eq_oprs[i],
+ paramname, pk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, " FOR SHARE OF x");
+ appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
RI_FKey_noaction_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
int i;
- int match_type;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
- if (ri_Check_Pk_Match(pk_rel, fk_rel,
- old_row, trigdata->tg_trigger->tgoid,
- match_type, tgnargs, tgargs))
+ if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
{
/*
* There's either another row, or no row could match this one. In
return PointerGetDatum(NULL);
}
- switch (match_type)
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE CASCADE
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_NOACTION_DEL_CHECKREF,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_NOACTION_DEL_CHECKREF);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+ * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+ appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+ fkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, " FOR SHARE OF x");
+ appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL restrict delete.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
RI_QueryKey qkey;
void *qplan;
int i;
- int match_type;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
- switch (match_type)
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE CASCADE
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_NOACTION_UPD_CHECKREF,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_NOACTION_UPD_CHECKREF);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
/*
* No need to check anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
- if (ri_Check_Pk_Match(pk_rel, fk_rel,
- old_row, trigdata->tg_trigger->tgoid,
- match_type, tgnargs, tgargs))
+ if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
{
/*
* There's either another row, or no row could match this one.
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+ * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+ appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+ fkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, " FOR SHARE OF x");
+ appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL noaction update.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_cascade_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
*/
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual DELETE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE CASCADE
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CASCADE_DEL_DODELETE,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_CASCADE_DEL_DODELETE);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * DELETE FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+ * DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "DELETE FROM ONLY %s", fkrelname);
+ appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_DELETE,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL cascaded delete.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
*/
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON UPDATE CASCADE
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CASCADE_UPD_DOUPDATE,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_CASCADE_UPD_DOUPDATE);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
/*
* No need to do anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
+ StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
- * WHERE fkatt1 = $n [AND ...]
+ * WHERE $n = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes. Note that we are assuming
+ * there is an assignment cast from the PK to the FK type;
+ * else the parser will fail.
* ----------
*/
+ initStringInfo(&querybuf);
+ initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
+ appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
+ for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = $%d",
- querysep, attname, i + 1);
- snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
- qualsep, attname, j + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ appendStringInfo(&querybuf,
+ "%s %s = $%d",
+ querysep, attname, i + 1);
+ sprintf(paramname, "$%d", j + 1);
+ ri_GenerateQual(&qualbuf, qualsep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = ",";
qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- queryoids[j] = queryoids[i];
+ queryoids[i] = pk_type;
+ queryoids[j] = pk_type;
}
- strcat(querystr, qualstr);
+ appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs * 2, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, new_row,
true, /* must detect new rows */
SPI_OK_UPDATE,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL cascade update.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_restrict_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
*/
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE CASCADE
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_RESTRICT_DEL_CHECKREF,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_RESTRICT_DEL_CHECKREF);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+ * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+ appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+ fkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, " FOR SHARE OF x");
+ appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL restrict delete.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
*/
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE CASCADE
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_RESTRICT_UPD_CHECKREF,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_RESTRICT_UPD_CHECKREF);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
/*
* No need to check anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowShareLock);
return PointerGetDatum(NULL);
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
- * SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+ * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+ appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+ fkrelname);
querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
- querysep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&querybuf, querysep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, " FOR SHARE OF x");
+ appendStringInfo(&querybuf, " FOR SHARE OF x");
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_SELECT,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL restrict update.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_setnull_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
*/
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE SET NULL
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_DEL_DOUPDATE,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_SETNULL_DEL_DOUPDATE);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
*/
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
+ StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
- * WHERE fkatt1 = $1 [AND ...]
+ * WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
+ initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
+ appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
- querysep, attname);
- snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ appendStringInfo(&querybuf,
+ "%s %s = NULL",
+ querysep, attname);
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&qualbuf, qualsep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = ",";
qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, qualstr);
+ appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare and save the plan */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, true);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL set null delete.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
RI_QueryKey qkey;
void *qplan;
int i;
- int match_type;
bool use_cached_query;
/*
*/
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
- switch (match_type)
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON UPDATE SET NULL
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_UPD_DOUPDATE,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_SETNULL_UPD_DOUPDATE);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
/*
* No need to do anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
* know that the old key value has no NULLs (see above).
*/
- use_cached_query = match_type == RI_MATCH_TYPE_FULL ||
+ use_cached_query = (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) ||
ri_AllKeysUnequal(pk_rel, old_row, new_row,
- &qkey, RI_KEYPAIR_PK_IDX);
+ &riinfo, true);
/*
* Fetch or prepare a saved plan for the set null update operation
if (!use_cached_query ||
(qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
+ StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
- * WHERE fkatt1 = $1 [AND ...]
+ * WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
+ initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
+ appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+ quoteOneName(attname,
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
/*
* MATCH <unspecified> - only change columns corresponding
* to changed columns in pk_rel's key
*/
- if (match_type == RI_MATCH_TYPE_FULL ||
- !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
+ if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
+ !ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+ &riinfo, true))
{
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
- querysep, attname);
+ appendStringInfo(&querybuf,
+ "%s %s = NULL",
+ querysep, attname);
querysep = ",";
}
- snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i + 1);
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&qualbuf, qualsep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, qualstr);
+ appendStringInfoString(&querybuf, qualbuf.data);
/*
* Prepare the plan. Save it only if we're building the
* "standard" plan.
*/
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel,
use_cached_query);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL set null update.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple old_row;
*/
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
old_row = trigdata->tg_trigtuple;
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON DELETE SET DEFAULT
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_DEL_DOUPDATE,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_SETNULL_DEL_DOUPDATE);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
* default value could potentially change between calls.
*/
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
+ StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
- * WHERE fkatt1 = $1 [AND ...]
+ * WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
+ initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
+ appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT",
- querysep, attname);
- snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i + 1);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ appendStringInfo(&querybuf,
+ "%s %s = DEFAULT",
+ querysep, attname);
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&qualbuf, qualsep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
querysep = ",";
qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, qualstr);
+ appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare the plan, don't save it */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, false);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL set null delete.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
void *qplan;
- int match_type;
/*
* Check that this is a valid trigger call on the right time and event.
*/
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
+ /*
+ * Get arguments.
+ */
+ ri_FetchConstraintInfo(&riinfo,
+ trigdata->tg_trigger, trigdata->tg_relation, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return PointerGetDatum(NULL);
/*
* fk_rel is opened in RowExclusiveLock mode since that's what our
* eventual UPDATE will get on it.
*/
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+ fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
- switch (match_type)
+ switch (riinfo.confmatchtype)
{
/* ----------
* SQL3 11.9 <referential constraint definition>
* ... ON UPDATE SET DEFAULT
* ----------
*/
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_DEL_DOUPDATE,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_SETNULL_DEL_DOUPDATE);
switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
{
/*
* No need to do anything if old and new keys are equal
*/
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
+ if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
{
heap_close(fk_rel, RowExclusiveLock);
return PointerGetDatum(NULL);
* default value could potentially change between calls.
*/
{
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+ StringInfoData querybuf;
+ StringInfoData qualbuf;
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
+ char paramname[16];
const char *querysep;
const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS];
/* ----------
* The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
- * WHERE fkatt1 = $1 [AND ...]
+ * WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
- * corresponding PK attributes. Thus, ri_PlanCheck could
- * eventually fail if the parser cannot identify some way
- * how to compare these two types by '='.
+ * corresponding PK attributes.
* ----------
*/
+ initStringInfo(&querybuf);
+ initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel);
- snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
+ appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
querysep = "";
qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
+ for (i = 0; i < riinfo.nkeys; i++)
{
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
/*
* MATCH <unspecified> - only change columns corresponding
* to changed columns in pk_rel's key
*/
- if (match_type == RI_MATCH_TYPE_FULL ||
- !ri_OneKeyEqual(pk_rel, i, old_row,
- new_row, &qkey, RI_KEYPAIR_PK_IDX))
+ if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
+ !ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+ &riinfo, true))
{
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT",
- querysep, attname);
+ appendStringInfo(&querybuf,
+ "%s %s = DEFAULT",
+ querysep, attname);
querysep = ",";
}
- snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i + 1);
+ sprintf(paramname, "$%d", i + 1);
+ ri_GenerateQual(&qualbuf, qualsep,
+ paramname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ attname, fk_type);
qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+ queryoids[i] = pk_type;
}
- strcat(querystr, qualstr);
+ appendStringInfoString(&querybuf, qualbuf.data);
/* Prepare the plan, don't save it */
- qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
&qkey, fk_rel, pk_rel, false);
}
old_row, NULL,
true, /* must detect new rows */
SPI_OK_UPDATE,
- tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+ NameStr(riinfo.conname));
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
/*
* Handle MATCH PARTIAL set null delete.
*/
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
/*
* Never reached
*/
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return PointerGetDatum(NULL);
}
RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
HeapTuple old_row, HeapTuple new_row)
{
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation fk_rel;
RI_QueryKey qkey;
/*
- * Check for the correct # of call arguments
+ * Get arguments.
*/
- tgnargs = trigger->tgnargs;
- tgargs = trigger->tgargs;
- if (tgnargs < 4 ||
- tgnargs > RI_MAX_ARGUMENTS ||
- (tgnargs % 2) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
- errmsg("function \"%s\" called with wrong number of trigger arguments",
- "RI_FKey_keyequal_upd")));
+ ri_FetchConstraintInfo(&riinfo, trigger, pk_rel, true);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return true;
- if (!OidIsValid(trigger->tgconstrrelid))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("no target table given for trigger \"%s\" on table \"%s\"",
- trigger->tgname,
- RelationGetRelationName(pk_rel)),
- errhint("Remove this referential integrity trigger and its mates, "
- "then do ALTER TABLE ADD CONSTRAINT.")));
-
- fk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock);
+ fk_rel = heap_open(riinfo.fk_relid, AccessShareLock);
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigger->tgoid,
- RI_PLAN_KEYEQUAL_UPD,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_KEYEQUAL_UPD);
+
heap_close(fk_rel, AccessShareLock);
/* Return if key's are equal */
- return ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX);
+ return ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true);
/* Handle MATCH PARTIAL set null delete. */
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
}
/* Never reached */
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return false;
}
RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel,
HeapTuple old_row, HeapTuple new_row)
{
- int tgnargs;
- char **tgargs;
+ RI_ConstraintInfo riinfo;
Relation pk_rel;
RI_QueryKey qkey;
/*
- * Check for the correct # of call arguments
+ * Get arguments.
*/
- tgnargs = trigger->tgnargs;
- tgargs = trigger->tgargs;
- if (tgnargs < 4 ||
- tgnargs > RI_MAX_ARGUMENTS ||
- (tgnargs % 2) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
- errmsg("function \"%s\" called with wrong number of trigger arguments",
- "RI_FKey_keyequal_upd")));
+ ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
/*
* Nothing to do if no column names to compare given
*/
- if (tgnargs == 4)
+ if (riinfo.nkeys == 0)
return true;
- if (!OidIsValid(trigger->tgconstrrelid))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("no target table given for trigger \"%s\" on table \"%s\"",
- trigger->tgname,
- RelationGetRelationName(fk_rel)),
- errhint("Remove this referential integrity trigger and its mates, "
- "then do ALTER TABLE ADD CONSTRAINT.")));
-
- pk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock);
+ pk_rel = heap_open(riinfo.pk_relid, AccessShareLock);
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+ switch (riinfo.confmatchtype)
{
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigger->tgoid,
- RI_PLAN_KEYEQUAL_UPD,
- fk_rel, pk_rel,
- tgnargs, tgargs);
+ case FKCONSTR_MATCH_UNSPECIFIED:
+ case FKCONSTR_MATCH_FULL:
+ ri_BuildQueryKeyFull(&qkey, &riinfo,
+ RI_PLAN_KEYEQUAL_UPD);
heap_close(pk_rel, AccessShareLock);
/* Return if key's are equal */
- return ri_KeysEqual(fk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_FK_IDX);
+ return ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false);
/* Handle MATCH PARTIAL set null delete. */
- case RI_MATCH_TYPE_PARTIAL:
+ case FKCONSTR_MATCH_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
}
/* Never reached */
- elog(ERROR, "invalid match_type");
+ elog(ERROR, "invalid confmatchtype");
return false;
}
* ----------
*/
bool
-RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel)
+RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
{
- const char *constrname = fkconstraint->constr_name;
- char querystr[MAX_QUOTED_REL_NAME_LEN * 2 + 250 +
- (MAX_QUOTED_NAME_LEN + 32) * ((RI_MAX_NUMKEYS * 4) + 1)];
+ RI_ConstraintInfo riinfo;
+ const char *constrname = trigger->tgname;
+ StringInfoData querybuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
- char relname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- char fkattname[MAX_QUOTED_NAME_LEN];
+ char fkrelname[MAX_QUOTED_REL_NAME_LEN];
+ char pkattname[MAX_QUOTED_NAME_LEN + 3];
+ char fkattname[MAX_QUOTED_NAME_LEN + 3];
const char *sep;
- ListCell *l;
- ListCell *l2;
+ int i;
int old_work_mem;
char workmembuf[32];
int spi_result;
*
* XXX are there any other show-stopper conditions to check?
*/
- if (pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ if (pg_class_aclcheck(RelationGetRelid(fk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
return false;
- if (pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ if (pg_class_aclcheck(RelationGetRelid(pk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
return false;
+ ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
+
/*----------
* The query string built is:
* SELECT fk.keycols FROM ONLY relname fk
* (fk.keycol1 IS NOT NULL [OR ...])
*----------
*/
-
- sprintf(querystr, "SELECT ");
+ initStringInfo(&querybuf);
+ appendStringInfo(&querybuf, "SELECT ");
sep = "";
- foreach(l, fkconstraint->fk_attrs)
+ for (i = 0; i < riinfo.nkeys; i++)
{
- quoteOneName(attname, strVal(lfirst(l)));
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
- "%sfk.%s", sep, attname);
+ quoteOneName(fkattname,
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
sep = ", ";
}
- quoteRelationName(pkrelname, pkrel);
- quoteRelationName(relname, rel);
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
- " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON (",
- relname, pkrelname);
+ quoteRelationName(pkrelname, pk_rel);
+ quoteRelationName(fkrelname, fk_rel);
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
- sep = "";
- forboth(l, fkconstraint->pk_attrs, l2, fkconstraint->fk_attrs)
+ strcpy(pkattname, "pk.");
+ strcpy(fkattname, "fk.");
+ sep = "(";
+ for (i = 0; i < riinfo.nkeys; i++)
{
- quoteOneName(attname, strVal(lfirst(l)));
- quoteOneName(fkattname, strVal(lfirst(l2)));
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
- "%spk.%s=fk.%s",
- sep, attname, fkattname);
- sep = " AND ";
+ Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+ Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
+ quoteOneName(pkattname + 3,
+ RIAttName(pk_rel, riinfo.pk_attnums[i]));
+ quoteOneName(fkattname + 3,
+ RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ ri_GenerateQual(&querybuf, sep,
+ pkattname, pk_type,
+ riinfo.pf_eq_oprs[i],
+ fkattname, fk_type);
+ sep = "AND";
}
/*
* It's sufficient to test any one pk attribute for null to detect a join
* failure.
*/
- quoteOneName(attname, strVal(linitial(fkconstraint->pk_attrs)));
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
- ") WHERE pk.%s IS NULL AND (", attname);
+ quoteOneName(pkattname, RIAttName(pk_rel, riinfo.pk_attnums[0]));
+ appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
sep = "";
- foreach(l, fkconstraint->fk_attrs)
+ for (i = 0; i < riinfo.nkeys; i++)
{
- quoteOneName(attname, strVal(lfirst(l)));
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
- "%sfk.%s IS NOT NULL",
- sep, attname);
- switch (fkconstraint->fk_matchtype)
+ quoteOneName(fkattname, RIAttName(fk_rel, riinfo.fk_attnums[i]));
+ appendStringInfo(&querybuf,
+ "%sfk.%s IS NOT NULL",
+ sep, fkattname);
+ switch (riinfo.confmatchtype)
{
case FKCONSTR_MATCH_UNSPECIFIED:
sep = " AND ";
break;
default:
elog(ERROR, "unrecognized match type: %d",
- fkconstraint->fk_matchtype);
+ riinfo.confmatchtype);
break;
}
}
- snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
- ")");
+ appendStringInfo(&querybuf, ")");
/*
* Temporarily increase work_mem so that the check query can be executed
* Generate the plan. We don't need to cache it, and there are no
* arguments to the plan.
*/
- qplan = SPI_prepare(querystr, 0, NULL);
+ qplan = SPI_prepare(querybuf.data, 0, NULL);
if (qplan == NULL)
- elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
+ elog(ERROR, "SPI_prepare returned %d for %s",
+ SPI_result, querybuf.data);
/*
* Run the plan. For safety we force a current snapshot to be used. (In
{
HeapTuple tuple = SPI_tuptable->vals[0];
TupleDesc tupdesc = SPI_tuptable->tupdesc;
- int nkeys = list_length(fkconstraint->fk_attrs);
- int i;
RI_QueryKey qkey;
/*
* complain about that rather than the lack of a match. MATCH FULL
* disallows partially-null FK rows.
*/
- if (fkconstraint->fk_matchtype == FKCONSTR_MATCH_FULL)
+ if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL)
{
bool isnull = false;
- for (i = 1; i <= nkeys; i++)
+ for (i = 1; i <= riinfo.nkeys; i++)
{
(void) SPI_getbinval(tuple, tupdesc, i, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
- RelationGetRelationName(rel),
+ RelationGetRelationName(fk_rel),
constrname),
errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
}
*/
MemSet(&qkey, 0, sizeof(qkey));
qkey.constr_queryno = RI_PLAN_CHECK_LOOKUPPK;
- qkey.nkeypairs = nkeys;
- for (i = 0; i < nkeys; i++)
+ qkey.nkeypairs = riinfo.nkeys;
+ for (i = 0; i < riinfo.nkeys; i++)
qkey.keypair[i][RI_KEYPAIR_FK_IDX] = i + 1;
ri_ReportViolation(&qkey, constrname,
- pkrel, rel,
+ pk_rel, fk_rel,
tuple, tupdesc,
false);
}
quoteOneName(buffer, RelationGetRelationName(rel));
}
-
-/* ----------
- * ri_DetermineMatchType -
+/*
+ * ri_GenerateQual --- generate a WHERE clause equating two variables
*
- * Convert the MATCH TYPE string into a switchable int
- * ----------
+ * The idea is to append " sep leftop op rightop" to buf. The complexity
+ * comes from needing to be sure that the parser will select the desired
+ * operator. We always name the operator using OPERATOR(schema.op) syntax
+ * (readability isn't a big priority here). We have to emit casts too,
+ * if either input isn't already the input type of the operator.
*/
-static int
-ri_DetermineMatchType(char *str)
+static void
+ri_GenerateQual(StringInfo buf,
+ const char *sep,
+ const char *leftop, Oid leftoptype,
+ Oid opoid,
+ const char *rightop, Oid rightoptype)
{
- if (strcmp(str, "UNSPECIFIED") == 0)
- return RI_MATCH_TYPE_UNSPECIFIED;
- if (strcmp(str, "FULL") == 0)
- return RI_MATCH_TYPE_FULL;
- if (strcmp(str, "PARTIAL") == 0)
- return RI_MATCH_TYPE_PARTIAL;
-
- elog(ERROR, "unrecognized referential integrity match type \"%s\"", str);
- return 0;
+ HeapTuple opertup;
+ Form_pg_operator operform;
+ char *oprname;
+ char *nspname;
+
+ opertup = SearchSysCache(OPEROID,
+ ObjectIdGetDatum(opoid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(opertup))
+ elog(ERROR, "cache lookup failed for operator %u", opoid);
+ operform = (Form_pg_operator) GETSTRUCT(opertup);
+ Assert(operform->oprkind == 'b');
+ oprname = NameStr(operform->oprname);
+
+ nspname = get_namespace_name(operform->oprnamespace);
+
+ appendStringInfo(buf, " %s %s", sep, leftop);
+ if (leftoptype != operform->oprleft)
+ appendStringInfo(buf, "::%s", format_type_be(operform->oprleft));
+ appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
+ appendStringInfoString(buf, oprname);
+ appendStringInfo(buf, ") %s", rightop);
+ if (rightoptype != operform->oprright)
+ appendStringInfo(buf, "::%s", format_type_be(operform->oprright));
+
+ ReleaseSysCache(opertup);
}
-
/* ----------
* ri_BuildQueryKeyFull -
*
* Build up a new hashtable key for a prepared SPI plan of a
- * constraint trigger of MATCH FULL. The key consists of:
+ * constraint trigger of MATCH FULL.
*
- * constr_type is FULL
- * constr_id is the OID of the pg_trigger row that invoked us
- * constr_queryno is an internal number of the query inside the proc
- * fk_relid is the OID of referencing relation
- * pk_relid is the OID of referenced relation
- * nkeypairs is the number of keypairs
- * following are the attribute number keypairs of the trigger invocation
+ * key: output argument, *key is filled in based on the other arguments
+ * riinfo: info from pg_constraint entry
+ * constr_queryno: an internal number of the query inside the proc
*
* At least for MATCH FULL this builds a unique key per plan.
* ----------
*/
static void
-ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
- Relation fk_rel, Relation pk_rel,
- int argc, char **argv)
+ri_BuildQueryKeyFull(RI_QueryKey *key, const RI_ConstraintInfo *riinfo,
+ int32 constr_queryno)
{
int i;
- int j;
- int fno;
- /*
- * Initialize the key and fill in type, oid's and number of keypairs
- */
- memset(key, 0, sizeof(RI_QueryKey));
- key->constr_type = RI_MATCH_TYPE_FULL;
- key->constr_id = constr_id;
+ MemSet(key, 0, sizeof(RI_QueryKey));
+ key->constr_type = FKCONSTR_MATCH_FULL;
+ key->constr_id = riinfo->constraint_id;
key->constr_queryno = constr_queryno;
- key->fk_relid = fk_rel->rd_id;
- key->pk_relid = pk_rel->rd_id;
- key->nkeypairs = (argc - RI_FIRST_ATTNAME_ARGNO) / 2;
-
- /*
- * Lookup the attribute numbers of the arguments to the trigger call and
- * fill in the keypairs.
- */
- for (i = 0, j = RI_FIRST_ATTNAME_ARGNO; j < argc; i++, j += 2)
+ key->fk_relid = riinfo->fk_relid;
+ key->pk_relid = riinfo->pk_relid;
+ key->nkeypairs = riinfo->nkeys;
+ for (i = 0; i < riinfo->nkeys; i++)
{
- fno = SPI_fnumber(fk_rel->rd_att, argv[j]);
- if (fno == SPI_ERROR_NOATTRIBUTE)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("table \"%s\" does not have column \"%s\" referenced by constraint \"%s\"",
- RelationGetRelationName(fk_rel),
- argv[j],
- argv[RI_CONSTRAINT_NAME_ARGNO])));
- key->keypair[i][RI_KEYPAIR_FK_IDX] = fno;
-
- fno = SPI_fnumber(pk_rel->rd_att, argv[j + 1]);
- if (fno == SPI_ERROR_NOATTRIBUTE)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("table \"%s\" does not have column \"%s\" referenced by constraint \"%s\"",
- RelationGetRelationName(pk_rel),
- argv[j + 1],
- argv[RI_CONSTRAINT_NAME_ARGNO])));
- key->keypair[i][RI_KEYPAIR_PK_IDX] = fno;
+ key->keypair[i][RI_KEYPAIR_FK_IDX] = riinfo->fk_attnums[i];
+ key->keypair[i][RI_KEYPAIR_PK_IDX] = riinfo->pk_attnums[i];
}
}
ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
if (!CALLED_AS_TRIGGER(fcinfo))
ereport(ERROR,
errmsg("function \"%s\" must be fired for DELETE", funcname)));
break;
}
+}
- /*
- * Check for the correct # of call arguments
- */
- tgnargs = trigdata->tg_trigger->tgnargs;
- if (tgnargs < 4 ||
- tgnargs > RI_MAX_ARGUMENTS ||
- (tgnargs % 2) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
- errmsg("function \"%s\" called with wrong number of trigger arguments",
- funcname)));
+
+/*
+ * Fetch the pg_constraint entry for the FK constraint, and fill *riinfo
+ */
+static void
+ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
+ Trigger *trigger, Relation trig_rel, bool rel_is_pk)
+{
+ Oid constraintOid = trigger->tgconstraint;
+ HeapTuple tup;
+ Form_pg_constraint conForm;
+ Datum adatum;
+ bool isNull;
+ ArrayType *arr;
+ int numkeys;
/*
- * Check that tgconstrrelid is known. We need to check here because of
- * ancient pg_dump bug; see notes in CreateTrigger().
+ * Check that the FK constraint's OID is available; it might not be
+ * if we've been invoked via an ordinary trigger or an old-style
+ * "constraint trigger".
*/
- if (!OidIsValid(trigdata->tg_trigger->tgconstrrelid))
+ if (!OidIsValid(constraintOid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("no target table given for trigger \"%s\" on table \"%s\"",
- trigdata->tg_trigger->tgname,
- RelationGetRelationName(trigdata->tg_relation)),
+ errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"",
+ trigger->tgname, RelationGetRelationName(trig_rel)),
errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT.")));
+
+ /* OK, fetch the tuple */
+ tup = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(constraintOid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
+ conForm = (Form_pg_constraint) GETSTRUCT(tup);
+
+ /* Do some easy cross-checks against the trigger call data */
+ if (rel_is_pk)
+ {
+ if (conForm->contype != CONSTRAINT_FOREIGN ||
+ conForm->conrelid != trigger->tgconstrrelid ||
+ conForm->confrelid != RelationGetRelid(trig_rel))
+ elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
+ trigger->tgname, RelationGetRelationName(trig_rel));
+ }
+ else
+ {
+ if (conForm->contype != CONSTRAINT_FOREIGN ||
+ conForm->conrelid != RelationGetRelid(trig_rel) ||
+ conForm->confrelid != trigger->tgconstrrelid)
+ elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
+ trigger->tgname, RelationGetRelationName(trig_rel));
+ }
+
+ /* And extract data */
+ riinfo->constraint_id = constraintOid;
+ memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData));
+ riinfo->pk_relid = conForm->confrelid;
+ riinfo->fk_relid = conForm->conrelid;
+ riinfo->confupdtype = conForm->confupdtype;
+ riinfo->confdeltype = conForm->confdeltype;
+ riinfo->confmatchtype = conForm->confmatchtype;
+
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify that.
+ * We don't need to use deconstruct_array() since the array data is
+ * just going to look like a C array of values.
+ */
+ adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conkey, &isNull);
+ if (isNull)
+ elog(ERROR, "null conkey for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys < 0 ||
+ numkeys > RI_MAX_NUMKEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "conkey is not a 1-D smallint array");
+ riinfo->nkeys = numkeys;
+ memcpy(riinfo->fk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16));
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confkey, &isNull);
+ if (isNull)
+ elog(ERROR, "null confkey for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys != riinfo->nkeys ||
+ numkeys > RI_MAX_NUMKEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "confkey is not a 1-D smallint array");
+ memcpy(riinfo->pk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16));
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conpfeqop, &isNull);
+ if (isNull)
+ elog(ERROR, "null conpfeqop for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys != riinfo->nkeys ||
+ numkeys > RI_MAX_NUMKEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "conpfeqop is not a 1-D Oid array");
+ memcpy(riinfo->pf_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid));
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conppeqop, &isNull);
+ if (isNull)
+ elog(ERROR, "null conppeqop for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys != riinfo->nkeys ||
+ numkeys > RI_MAX_NUMKEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "conppeqop is not a 1-D Oid array");
+ memcpy(riinfo->pp_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid));
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conffeqop, &isNull);
+ if (isNull)
+ elog(ERROR, "null conffeqop for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys != riinfo->nkeys ||
+ numkeys > RI_MAX_NUMKEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "conffeqop is not a 1-D Oid array");
+ memcpy(riinfo->ff_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid));
+
+ ReleaseSysCache(tup);
}
* Build up a new hashtable key for a prepared SPI plan of a
* check for PK rows in noaction triggers.
*
- * constr_type is FULL
- * constr_id is the OID of the pg_trigger row that invoked us
- * constr_queryno is an internal number of the query inside the proc
- * pk_relid is the OID of referenced relation
- * nkeypairs is the number of keypairs
- * following are the attribute number keypairs of the trigger invocation
+ * key: output argument, *key is filled in based on the other arguments
+ * riinfo: info from pg_constraint entry
+ * constr_queryno: an internal number of the query inside the proc
*
* At least for MATCH FULL this builds a unique key per plan.
* ----------
*/
static void
-ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
- Relation pk_rel,
- int argc, char **argv)
+ri_BuildQueryKeyPkCheck(RI_QueryKey *key, const RI_ConstraintInfo *riinfo,
+ int32 constr_queryno)
{
int i;
- int j;
- int fno;
- /*
- * Initialize the key and fill in type, oid's and number of keypairs
- */
- memset((void *) key, 0, sizeof(RI_QueryKey));
- key->constr_type = RI_MATCH_TYPE_FULL;
- key->constr_id = constr_id;
+ MemSet(key, 0, sizeof(RI_QueryKey));
+ key->constr_type = FKCONSTR_MATCH_FULL;
+ key->constr_id = riinfo->constraint_id;
key->constr_queryno = constr_queryno;
- key->fk_relid = 0;
- key->pk_relid = pk_rel->rd_id;
- key->nkeypairs = (argc - RI_FIRST_ATTNAME_ARGNO) / 2;
-
- /*
- * Lookup the attribute numbers of the arguments to the trigger call and
- * fill in the keypairs.
- */
- for (i = 0, j = RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX; j < argc; i++, j += 2)
+ key->fk_relid = InvalidOid;
+ key->pk_relid = riinfo->pk_relid;
+ key->nkeypairs = riinfo->nkeys;
+ for (i = 0; i < riinfo->nkeys; i++)
{
- fno = SPI_fnumber(pk_rel->rd_att, argv[j]);
- if (fno == SPI_ERROR_NOATTRIBUTE)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("table \"%s\" does not have column \"%s\" referenced by constraint \"%s\"",
- RelationGetRelationName(pk_rel),
- argv[j],
- argv[RI_CONSTRAINT_NAME_ARGNO])));
- key->keypair[i][RI_KEYPAIR_PK_IDX] = fno;
key->keypair[i][RI_KEYPAIR_FK_IDX] = 0;
+ key->keypair[i][RI_KEYPAIR_PK_IDX] = riinfo->pk_attnums[i];
}
}
/* ----------
* ri_InitHashTables -
*
- * Initialize our internal hash table for prepared
- * query plans.
+ * Initialize our internal hash tables for prepared
+ * query plans and comparison operators.
* ----------
*/
static void
ctl.hash = tag_hash;
ri_query_cache = hash_create("RI query cache", RI_INIT_QUERYHASHSIZE,
&ctl, HASH_ELEM | HASH_FUNCTION);
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(RI_CompareKey);
+ ctl.entrysize = sizeof(RI_CompareHashEntry);
+ ctl.hash = tag_hash;
+ ri_compare_cache = hash_create("RI compare cache", RI_INIT_QUERYHASHSIZE,
+ &ctl, HASH_ELEM | HASH_FUNCTION);
}
*/
static bool
ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx)
+ const RI_ConstraintInfo *riinfo, bool rel_is_pk)
{
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ const int16 *attnums;
+ const Oid *eq_oprs;
int i;
- Oid typeid;
- Datum oldvalue;
- Datum newvalue;
- bool isnull;
- for (i = 0; i < key->nkeypairs; i++)
+ if (rel_is_pk)
+ {
+ attnums = riinfo->pk_attnums;
+ eq_oprs = riinfo->pp_eq_oprs;
+ }
+ else
+ {
+ attnums = riinfo->fk_attnums;
+ eq_oprs = riinfo->ff_eq_oprs;
+ }
+
+ for (i = 0; i < riinfo->nkeys; i++)
{
+ Datum oldvalue;
+ Datum newvalue;
+ bool isnull;
+
/*
* Get one attribute's oldvalue. If it is NULL - they're not equal.
*/
- oldvalue = SPI_getbinval(oldtup, rel->rd_att,
- key->keypair[i][pairidx], &isnull);
+ oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
if (isnull)
return false;
/*
- * Get one attribute's oldvalue. If it is NULL - they're not equal.
+ * Get one attribute's newvalue. If it is NULL - they're not equal.
*/
- newvalue = SPI_getbinval(newtup, rel->rd_att,
- key->keypair[i][pairidx], &isnull);
+ newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
if (isnull)
return false;
/*
- * Get the attribute's type OID and call the '=' operator to compare
- * the values.
+ * Compare them with the appropriate equality operator.
*/
- typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
- if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
+ if (!ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
+ oldvalue, newvalue))
return false;
}
*/
static bool
ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx)
+ const RI_ConstraintInfo *riinfo, bool rel_is_pk)
{
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ const int16 *attnums;
+ const Oid *eq_oprs;
int i;
- Oid typeid;
- Datum oldvalue;
- Datum newvalue;
- bool isnull;
- bool keys_unequal;
- keys_unequal = true;
- for (i = 0; keys_unequal && i < key->nkeypairs; i++)
+ if (rel_is_pk)
{
+ attnums = riinfo->pk_attnums;
+ eq_oprs = riinfo->pp_eq_oprs;
+ }
+ else
+ {
+ attnums = riinfo->fk_attnums;
+ eq_oprs = riinfo->ff_eq_oprs;
+ }
+
+ for (i = 0; i < riinfo->nkeys; i++)
+ {
+ Datum oldvalue;
+ Datum newvalue;
+ bool isnull;
+
/*
- * Get one attributes oldvalue. If it is NULL - they're not equal.
+ * Get one attribute's oldvalue. If it is NULL - they're not equal.
*/
- oldvalue = SPI_getbinval(oldtup, rel->rd_att,
- key->keypair[i][pairidx], &isnull);
+ oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
if (isnull)
continue;
/*
- * Get one attributes oldvalue. If it is NULL - they're not equal.
+ * Get one attribute's newvalue. If it is NULL - they're not equal.
*/
- newvalue = SPI_getbinval(newtup, rel->rd_att,
- key->keypair[i][pairidx], &isnull);
+ newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
if (isnull)
continue;
/*
- * Get the attributes type OID and call the '=' operator to compare
- * the values.
+ * Compare them with the appropriate equality operator.
*/
- typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
- if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
- continue;
- keys_unequal = false;
+ if (ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
+ oldvalue, newvalue))
+ return false; /* found two equal items */
}
- return keys_unequal;
+ return true;
}
/* ----------
* ri_OneKeyEqual -
*
- * Check if one key value in OLD and NEW is equal.
+ * Check if one key value in OLD and NEW is equal. Note column is indexed
+ * from zero.
*
* ri_KeysEqual could call this but would run a bit slower. For
* now, let's duplicate the code.
*/
static bool
ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx)
+ const RI_ConstraintInfo *riinfo, bool rel_is_pk)
{
- Oid typeid;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ const int16 *attnums;
+ const Oid *eq_oprs;
Datum oldvalue;
Datum newvalue;
bool isnull;
+ if (rel_is_pk)
+ {
+ attnums = riinfo->pk_attnums;
+ eq_oprs = riinfo->pp_eq_oprs;
+ }
+ else
+ {
+ attnums = riinfo->fk_attnums;
+ eq_oprs = riinfo->ff_eq_oprs;
+ }
+
/*
- * Get one attributes oldvalue. If it is NULL - they're not equal.
+ * Get one attribute's oldvalue. If it is NULL - they're not equal.
*/
- oldvalue = SPI_getbinval(oldtup, rel->rd_att,
- key->keypair[column][pairidx], &isnull);
+ oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[column], &isnull);
if (isnull)
return false;
/*
- * Get one attributes oldvalue. If it is NULL - they're not equal.
+ * Get one attribute's newvalue. If it is NULL - they're not equal.
*/
- newvalue = SPI_getbinval(newtup, rel->rd_att,
- key->keypair[column][pairidx], &isnull);
+ newvalue = SPI_getbinval(newtup, tupdesc, attnums[column], &isnull);
if (isnull)
return false;
/*
- * Get the attributes type OID and call the '=' operator to compare the
- * values.
+ * Compare them with the appropriate equality operator.
*/
- typeid = SPI_gettypeid(rel->rd_att, key->keypair[column][pairidx]);
- if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
+ if (!ri_AttributesEqual(eq_oprs[column], RIAttType(rel, attnums[column]),
+ oldvalue, newvalue))
return false;
return true;
}
-
/* ----------
* ri_AttributesEqual -
*
- * Call the type specific '=' operator comparison function
- * for two values.
+ * Call the appropriate equality comparison operator for two values.
*
* NB: we have already checked that neither value is null.
* ----------
*/
static bool
-ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue)
+ri_AttributesEqual(Oid eq_opr, Oid typeid,
+ Datum oldvalue, Datum newvalue)
+{
+ RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid);
+
+ /* Do we need to cast the values? */
+ if (OidIsValid(entry->cast_func_finfo.fn_oid))
+ {
+ oldvalue = FunctionCall3(&entry->cast_func_finfo,
+ oldvalue,
+ Int32GetDatum(-1), /* typmod */
+ BoolGetDatum(false)); /* implicit coercion */
+ newvalue = FunctionCall3(&entry->cast_func_finfo,
+ newvalue,
+ Int32GetDatum(-1), /* typmod */
+ BoolGetDatum(false)); /* implicit coercion */
+ }
+
+ /* Apply the comparison operator */
+ return DatumGetBool(FunctionCall2(&entry->eq_opr_finfo,
+ oldvalue, newvalue));
+}
+
+/* ----------
+ * ri_HashCompareOp -
+ *
+ * See if we know how to compare two values, and create a new hash entry
+ * if not.
+ * ----------
+ */
+static RI_CompareHashEntry *
+ri_HashCompareOp(Oid eq_opr, Oid typeid)
{
- TypeCacheEntry *typentry;
+ RI_CompareKey key;
+ RI_CompareHashEntry *entry;
+ bool found;
/*
- * Find the data type in the typcache, and ask for eq_opr info.
+ * On the first call initialize the hashtable
*/
- typentry = lookup_type_cache(typeid, TYPECACHE_EQ_OPR_FINFO);
+ if (!ri_compare_cache)
+ ri_InitHashTables();
- if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_FUNCTION),
- errmsg("could not identify an equality operator for type %s",
- format_type_be(typeid))));
+ /*
+ * Find or create a hash entry. Note we're assuming RI_CompareKey
+ * contains no struct padding.
+ */
+ key.eq_opr = eq_opr;
+ key.typeid = typeid;
+ entry = (RI_CompareHashEntry *) hash_search(ri_compare_cache,
+ (void *) &key,
+ HASH_ENTER, &found);
+ if (!found)
+ entry->valid = false;
/*
- * Call the type specific '=' function
+ * If not already initialized, do so. Since we'll keep this hash entry
+ * for the life of the backend, put any subsidiary info for the function
+ * cache structs into TopMemoryContext.
*/
- return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo),
- oldvalue, newvalue));
+ if (!entry->valid)
+ {
+ Oid lefttype,
+ righttype,
+ castfunc;
+
+ /* We always need to know how to call the equality operator */
+ fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo,
+ TopMemoryContext);
+
+ /*
+ * If we chose to use a cast from FK to PK type, we may have to
+ * apply the cast function to get to the operator's input type.
+ */
+ op_input_types(eq_opr, &lefttype, &righttype);
+ Assert(lefttype == righttype);
+ if (typeid == lefttype)
+ castfunc = InvalidOid; /* simplest case */
+ else if (!find_coercion_pathway(lefttype, typeid, COERCION_IMPLICIT,
+ &castfunc))
+ {
+ /* If target is ANYARRAY, assume it's OK, else punt. */
+ if (lefttype != ANYARRAYOID)
+ elog(ERROR, "no conversion function from %s to %s",
+ format_type_be(typeid),
+ format_type_be(lefttype));
+ }
+ if (OidIsValid(castfunc))
+ fmgr_info_cxt(castfunc, &entry->cast_func_finfo,
+ TopMemoryContext);
+ else
+ entry->cast_func_finfo.fn_oid = InvalidOid;
+ entry->valid = true;
+ }
+
+ return entry;
}
+
/*
* Given a trigger function OID, determine whether it is an RI trigger,
* and if so whether it is attached to PK or FK relation.