From 7bddca3450cc8631e5bf05e43988cf10ae32230e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 14 Feb 2007 01:58:58 +0000 Subject: [PATCH] Fix up foreign-key mechanism so that there is a sound semantic basis for the equality checks it applies, instead of a random dependence on whatever operators might be named "=". The equality operators will now be selected from the opfamily of the unique index that the FK constraint depends on to enforce uniqueness of the referenced columns; therefore they are certain to be consistent with that index's notion of equality. Among other things this should fix the problem noted awhile back that pg_dump may fail for foreign-key constraints on user-defined types when the required operators aren't in the search path. This also means that the former warning condition about "foreign key constraint will require costly sequential scans" is gone: if the comparison condition isn't indexable then we'll reject the constraint entirely. All per past discussions. Along the way, make the RI triggers look into pg_constraint for their information, instead of using pg_trigger.tgargs; and get rid of the always error-prone fixed-size string buffers in ri_triggers.c in favor of building up the RI queries in StringInfo buffers. initdb forced due to columns added to pg_constraint and pg_trigger. --- doc/src/sgml/catalogs.sgml | 58 +- doc/src/sgml/trigger.sgml | 3 +- src/backend/catalog/aclchk.c | 4 +- src/backend/catalog/dependency.c | 34 +- src/backend/catalog/heap.c | 5 +- src/backend/catalog/index.c | 5 +- src/backend/catalog/namespace.c | 4 +- src/backend/catalog/pg_constraint.c | 188 ++- src/backend/catalog/pg_conversion.c | 6 +- src/backend/commands/conversioncmds.c | 6 +- src/backend/commands/explain.c | 6 +- src/backend/commands/tablecmds.c | 563 +++------ src/backend/commands/trigger.c | 125 +- src/backend/commands/typecmds.c | 5 +- src/backend/tcop/utility.c | 4 +- src/backend/utils/adt/ri_triggers.c | 1772 +++++++++++++++++------------ src/backend/utils/adt/ruleutils.c | 48 +- src/backend/utils/cache/lsyscache.c | 34 +- src/backend/utils/cache/syscache.c | 17 +- src/bin/pg_dump/pg_dump.c | 23 +- src/bin/psql/describe.c | 18 +- src/include/catalog/catversion.h | 4 +- src/include/catalog/indexing.h | 5 +- src/include/catalog/pg_constraint.h | 34 +- src/include/catalog/pg_trigger.h | 43 +- src/include/commands/trigger.h | 36 +- src/include/utils/lsyscache.h | 3 +- src/include/utils/rel.h | 3 +- src/include/utils/syscache.h | 75 +- src/test/regress/expected/alter_table.out | 48 +- src/test/regress/expected/foreign_key.out | 92 +- src/test/regress/sql/alter_table.sql | 35 +- src/test/regress/sql/foreign_key.sql | 46 +- 33 files changed, 1709 insertions(+), 1643 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index f5e15f1985..9a9f9b55be 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ - + @@ -1871,6 +1871,27 @@ + conpfeqop + oid[] + pg_operator.oid + If a foreign key, list of the equality operators for PK = FK comparisons + + + + conppeqop + oid[] + pg_operator.oid + If a foreign key, list of the equality operators for PK = PK comparisons + + + + conffeqop + oid[] + pg_operator.oid + If a foreign key, list of the equality operators for FK = FK comparisons + + + conbin text @@ -1899,8 +1920,8 @@ pg_class.relchecks needs to agree with the - number of check-constraint entries found in this table for the - given relation. + number of check-constraint entries found in this table for each + relation. @@ -4166,35 +4187,42 @@ tgisconstraint bool - True if trigger implements a referential integrity constraint + True if trigger is a constraint trigger tgconstrname name - Referential integrity constraint name + Constraint name, if a constraint trigger tgconstrrelid oid pg_class.oid - The table referenced by an referential integrity constraint + The table referenced by a referential integrity constraint + + + + tgconstraint + oid + pg_constraint.oid + The pg_constraint entry owning the trigger, if any tgdeferrable bool - True if deferrable + True if constraint trigger is deferrable tginitdeferred bool - True if initially deferred + True if constraint trigger is initially deferred @@ -4223,8 +4251,20 @@ + When tgconstraint is nonzero, + tgisconstraint must be true, and + tgconstrname, tgconstrrelid, + tgdeferrable, tginitdeferred are redundant + with the referenced pg_constraint entry. The reason we + keep these fields is that we support stand-alone constraint + triggers with no corresponding pg_constraint entry. + + + + + pg_class.reltriggers needs to agree with the - number of triggers found in this table for the given relation. + number of triggers found in this table for each relation. diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index 0479f57b0d..193b817397 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -1,4 +1,4 @@ - + Triggers @@ -467,6 +467,7 @@ typedef struct Trigger bool tgenabled; bool tgisconstraint; Oid tgconstrrelid; + Oid tgconstraint; bool tgdeferrable; bool tginitdeferred; int16 tgnargs; diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index b1210841ff..a2b64c78b3 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.136 2007/02/01 19:10:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.137 2007/02/14 01:58:56 tgl Exp $ * * NOTES * See acl.h. @@ -2314,7 +2314,7 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid) if (superuser_arg(roleid)) return true; - tuple = SearchSysCache(CONOID, + tuple = SearchSysCache(CONVOID, ObjectIdGetDatum(conv_oid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 2041125d4d..40591fd368 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.63 2007/02/01 19:10:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.64 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1758,29 +1758,16 @@ getObjectDescription(const ObjectAddress *object) case OCLASS_CONSTRAINT: { - Relation conDesc; - ScanKeyData skey[1]; - SysScanDesc rcscan; - HeapTuple tup; + HeapTuple conTup; Form_pg_constraint con; - conDesc = heap_open(ConstraintRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], - ObjectIdAttributeNumber, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(object->objectId)); - - rcscan = systable_beginscan(conDesc, ConstraintOidIndexId, true, - SnapshotNow, 1, skey); - - tup = systable_getnext(rcscan); - - if (!HeapTupleIsValid(tup)) - elog(ERROR, "could not find tuple for constraint %u", + conTup = SearchSysCache(CONSTROID, + ObjectIdGetDatum(object->objectId), + 0, 0, 0); + if (!HeapTupleIsValid(conTup)) + elog(ERROR, "cache lookup failed for constraint %u", object->objectId); - - con = (Form_pg_constraint) GETSTRUCT(tup); + con = (Form_pg_constraint) GETSTRUCT(conTup); if (OidIsValid(con->conrelid)) { @@ -1794,8 +1781,7 @@ getObjectDescription(const ObjectAddress *object) NameStr(con->conname)); } - systable_endscan(rcscan); - heap_close(conDesc, AccessShareLock); + ReleaseSysCache(conTup); break; } @@ -1803,7 +1789,7 @@ getObjectDescription(const ObjectAddress *object) { HeapTuple conTup; - conTup = SearchSysCache(CONOID, + conTup = SearchSysCache(CONVOID, ObjectIdGetDatum(object->objectId), 0, 0, 0); if (!HeapTupleIsValid(conTup)) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 1d0639bcf0..d29a6df21d 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.316 2007/01/05 22:19:24 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.317 2007/02/14 01:58:56 tgl Exp $ * * * INTERFACE ROUTINES @@ -1463,6 +1463,9 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin) InvalidOid, /* not a domain constraint */ InvalidOid, /* Foreign key fields */ NULL, + NULL, + NULL, + NULL, 0, ' ', ' ', diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 52554c3b21..ae8965e730 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.278 2007/02/01 19:10:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.279 2007/02/14 01:58:56 tgl Exp $ * * * INTERFACE ROUTINES @@ -680,6 +680,9 @@ index_create(Oid heapRelationId, InvalidOid, /* no domain */ InvalidOid, /* no foreign key */ NULL, + NULL, + NULL, + NULL, 0, ' ', ' ', diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 7c2cd4b8a4..2213192f78 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.91 2007/02/01 19:10:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.92 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1199,7 +1199,7 @@ ConversionIsVisible(Oid conid) Oid connamespace; bool visible; - contup = SearchSysCache(CONOID, + contup = SearchSysCache(CONVOID, ObjectIdGetDatum(conid), 0, 0, 0); if (!HeapTupleIsValid(contup)) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 53249facb7..ede6607b85 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.34 2007/01/05 22:19:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.35 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,8 +19,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_constraint.h" -#include "catalog/pg_depend.h" -#include "catalog/pg_trigger.h" +#include "catalog/pg_operator.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "utils/array.h" @@ -49,6 +48,9 @@ CreateConstraintEntry(const char *constraintName, Oid domainId, Oid foreignRelId, const int16 *foreignKey, + const Oid *pfEqOp, + const Oid *ppEqOp, + const Oid *ffEqOp, int foreignNKeys, char foreignUpdateType, char foreignDeleteType, @@ -65,6 +67,9 @@ CreateConstraintEntry(const char *constraintName, Datum values[Natts_pg_constraint]; ArrayType *conkeyArray; ArrayType *confkeyArray; + ArrayType *conpfeqopArray; + ArrayType *conppeqopArray; + ArrayType *conffeqopArray; NameData cname; int i; ObjectAddress conobject; @@ -92,16 +97,33 @@ CreateConstraintEntry(const char *constraintName, if (foreignNKeys > 0) { - Datum *confkey; + Datum *fkdatums; - confkey = (Datum *) palloc(foreignNKeys * sizeof(Datum)); + fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum)); for (i = 0; i < foreignNKeys; i++) - confkey[i] = Int16GetDatum(foreignKey[i]); - confkeyArray = construct_array(confkey, foreignNKeys, + fkdatums[i] = Int16GetDatum(foreignKey[i]); + confkeyArray = construct_array(fkdatums, foreignNKeys, INT2OID, 2, true, 's'); + for (i = 0; i < foreignNKeys; i++) + fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]); + conpfeqopArray = construct_array(fkdatums, foreignNKeys, + OIDOID, sizeof(Oid), true, 'i'); + for (i = 0; i < foreignNKeys; i++) + fkdatums[i] = ObjectIdGetDatum(ppEqOp[i]); + conppeqopArray = construct_array(fkdatums, foreignNKeys, + OIDOID, sizeof(Oid), true, 'i'); + for (i = 0; i < foreignNKeys; i++) + fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]); + conffeqopArray = construct_array(fkdatums, foreignNKeys, + OIDOID, sizeof(Oid), true, 'i'); } else + { confkeyArray = NULL; + conpfeqopArray = NULL; + conppeqopArray = NULL; + conffeqopArray = NULL; + } /* initialize nulls and values */ for (i = 0; i < Natts_pg_constraint; i++) @@ -132,6 +154,21 @@ CreateConstraintEntry(const char *constraintName, else nulls[Anum_pg_constraint_confkey - 1] = 'n'; + if (conpfeqopArray) + values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray); + else + nulls[Anum_pg_constraint_conpfeqop - 1] = 'n'; + + if (conppeqopArray) + values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(conppeqopArray); + else + nulls[Anum_pg_constraint_conppeqop - 1] = 'n'; + + if (conffeqopArray) + values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(conffeqopArray); + else + nulls[Anum_pg_constraint_conffeqop - 1] = 'n'; + /* * initialize the binary form of the check constraint. */ @@ -246,6 +283,36 @@ CreateConstraintEntry(const char *constraintName, recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL); } + if (foreignNKeys > 0) + { + /* + * Register normal dependencies on the equality operators that + * support a foreign-key constraint. If the PK and FK types + * are the same then all three operators for a column are the + * same; otherwise they are different. + */ + ObjectAddress oprobject; + + oprobject.classId = OperatorRelationId; + oprobject.objectSubId = 0; + + for (i = 0; i < foreignNKeys; i++) + { + oprobject.objectId = pfEqOp[i]; + recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); + if (ppEqOp[i] != pfEqOp[i]) + { + oprobject.objectId = ppEqOp[i]; + recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); + } + if (ffEqOp[i] != pfEqOp[i]) + { + oprobject.objectId = ffEqOp[i]; + recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL); + } + } + } + if (conExpr != NULL) { /* @@ -419,24 +486,16 @@ void RemoveConstraintById(Oid conId) { Relation conDesc; - ScanKeyData skey[1]; - SysScanDesc conscan; HeapTuple tup; Form_pg_constraint con; conDesc = heap_open(ConstraintRelationId, RowExclusiveLock); - ScanKeyInit(&skey[0], - ObjectIdAttributeNumber, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(conId)); - - conscan = systable_beginscan(conDesc, ConstraintOidIndexId, true, - SnapshotNow, 1, skey); - - tup = systable_getnext(conscan); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "could not find tuple for constraint %u", conId); + tup = SearchSysCache(CONSTROID, + ObjectIdGetDatum(conId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for constraint %u", conId); con = (Form_pg_constraint) GETSTRUCT(tup); /* @@ -505,98 +564,11 @@ RemoveConstraintById(Oid conId) simple_heap_delete(conDesc, &tup->t_self); /* Clean up */ - systable_endscan(conscan); + ReleaseSysCache(tup); heap_close(conDesc, RowExclusiveLock); } /* - * GetConstraintNameForTrigger - * Get the name of the constraint owning a trigger, if any - * - * Returns a palloc'd string, or NULL if no constraint can be found - */ -char * -GetConstraintNameForTrigger(Oid triggerId) -{ - char *result; - Oid constraintId = InvalidOid; - Relation depRel; - Relation conRel; - ScanKeyData key[2]; - SysScanDesc scan; - HeapTuple tup; - - /* - * We must grovel through pg_depend to find the owning constraint. Perhaps - * pg_trigger should have a column for the owning constraint ... but right - * now this is not performance-critical code. - */ - depRel = heap_open(DependRelationId, AccessShareLock); - - ScanKeyInit(&key[0], - Anum_pg_depend_classid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(TriggerRelationId)); - ScanKeyInit(&key[1], - Anum_pg_depend_objid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(triggerId)); - /* assume we can ignore objsubid for a trigger */ - - scan = systable_beginscan(depRel, DependDependerIndexId, true, - SnapshotNow, 2, key); - - while (HeapTupleIsValid(tup = systable_getnext(scan))) - { - Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup); - - if (foundDep->refclassid == ConstraintRelationId && - foundDep->deptype == DEPENDENCY_INTERNAL) - { - constraintId = foundDep->refobjid; - break; - } - } - - systable_endscan(scan); - - heap_close(depRel, AccessShareLock); - - if (!OidIsValid(constraintId)) - return NULL; /* no owning constraint found */ - - conRel = heap_open(ConstraintRelationId, AccessShareLock); - - ScanKeyInit(&key[0], - ObjectIdAttributeNumber, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(constraintId)); - - scan = systable_beginscan(conRel, ConstraintOidIndexId, true, - SnapshotNow, 1, key); - - tup = systable_getnext(scan); - - if (HeapTupleIsValid(tup)) - { - Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup); - - result = pstrdup(NameStr(con->conname)); - } - else - { - /* This arguably should be an error, but we'll just return NULL */ - result = NULL; - } - - systable_endscan(scan); - - heap_close(conRel, AccessShareLock); - - return result; -} - -/* * AlterConstraintNamespaces * Find any constraints belonging to the specified object, * and move them to the specified new namespace. diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c index c11f337eed..6377945285 100644 --- a/src/backend/catalog/pg_conversion.c +++ b/src/backend/catalog/pg_conversion.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_conversion.c,v 1.34 2007/01/05 22:19:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_conversion.c,v 1.35 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -146,7 +146,7 @@ ConversionDrop(Oid conversionOid, DropBehavior behavior) HeapTuple tuple; ObjectAddress object; - tuple = SearchSysCache(CONOID, + tuple = SearchSysCache(CONVOID, ObjectIdGetDatum(conversionOid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) @@ -313,7 +313,7 @@ pg_convert_using(PG_FUNCTION_ARGS) errmsg("conversion \"%s\" does not exist", NameListToString(parsed_name)))); - tuple = SearchSysCache(CONOID, + tuple = SearchSysCache(CONVOID, ObjectIdGetDatum(convoid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) diff --git a/src/backend/commands/conversioncmds.c b/src/backend/commands/conversioncmds.c index 349eadf24f..37cc0e6f57 100644 --- a/src/backend/commands/conversioncmds.c +++ b/src/backend/commands/conversioncmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.30 2007/01/05 22:19:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.31 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -146,7 +146,7 @@ RenameConversion(List *name, const char *newname) errmsg("conversion \"%s\" does not exist", NameListToString(name)))); - tup = SearchSysCacheCopy(CONOID, + tup = SearchSysCacheCopy(CONVOID, ObjectIdGetDatum(conversionOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ @@ -236,7 +236,7 @@ AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId) Assert(RelationGetRelid(rel) == ConversionRelationId); - tup = SearchSysCacheCopy(CONOID, + tup = SearchSysCacheCopy(CONVOID, ObjectIdGetDatum(conversionOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index ae9839641e..9ea32d7707 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.153 2007/01/05 22:19:26 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.154 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -327,8 +327,8 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt, if (instr->ntuples == 0) continue; - if (trig->tgisconstraint && - (conname = GetConstraintNameForTrigger(trig->tgoid)) != NULL) + if (OidIsValid(trig->tgconstraint) && + (conname = get_constraint_name(trig->tgconstraint)) != NULL) { appendStringInfo(&buf, "Trigger for constraint %s", conname); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 001bc57154..cea8ddeabf 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.213 2007/02/02 00:07:02 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.214 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,6 +28,7 @@ #include "catalog/pg_depend.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/toasting.h" @@ -139,6 +140,7 @@ typedef struct NewConstraint char *name; /* Constraint name, or NULL if none */ ConstrType contype; /* CHECK or FOREIGN */ Oid refrelid; /* PK rel, if FOREIGN */ + Oid conid; /* OID of pg_constraint entry, if FOREIGN */ Node *qual; /* Check expr or FkConstraint struct */ List *qualstate; /* Execution state for CHECK */ } NewConstraint; @@ -186,10 +188,9 @@ static Oid transformFkeyCheckAttrs(Relation pkrel, int numattrs, int16 *attnums, Oid *opclasses); static void validateForeignKeyConstraint(FkConstraint *fkconstraint, - Relation rel, Relation pkrel); + Relation rel, Relation pkrel, Oid constraintOid); static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, - Oid constrOid); -static char *fkMatchTypeToString(char match_type); + Oid constraintOid); static void ATController(Relation rel, List *cmds, bool recurse); static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing); @@ -256,11 +257,6 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname, static void ATExecAddInherit(Relation rel, RangeVar *parent); static void ATExecDropInherit(Relation rel, RangeVar *parent); static void copy_relation_data(Relation rel, SMgrRelation dst); -static void update_ri_trigger_args(Oid relid, - const char *oldname, - const char *newname, - bool fk_scan, - bool update_relname); /* ---------------------------------------------------------------- @@ -1615,21 +1611,6 @@ renameatt(Oid myrelid, heap_close(attrelation, RowExclusiveLock); - /* - * Update att name in any RI triggers associated with the relation. - */ - if (targetrelation->rd_rel->reltriggers > 0) - { - /* update tgargs column reference where att is primary key */ - update_ri_trigger_args(RelationGetRelid(targetrelation), - oldattname, newattname, - false, false); - /* update tgargs column reference where att is foreign key */ - update_ri_trigger_args(RelationGetRelid(targetrelation), - oldattname, newattname, - true, false); - } - relation_close(targetrelation, NoLock); /* close rel but keep lock */ } @@ -1709,226 +1690,12 @@ renamerel(Oid myrelid, const char *newrelname) TypeRename(oldrelname, namespaceId, newrelname); /* - * Update rel name in any RI triggers associated with the relation. - */ - if (relhastriggers) - { - /* update tgargs where relname is primary key */ - update_ri_trigger_args(myrelid, - oldrelname, - newrelname, - false, true); - /* update tgargs where relname is foreign key */ - update_ri_trigger_args(myrelid, - oldrelname, - newrelname, - true, true); - } - - /* * Close rel, but keep exclusive lock! */ relation_close(targetrelation, NoLock); } /* - * Scan pg_trigger for RI triggers that are on the specified relation - * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan - * is true). Update RI trigger args fields matching oldname to contain - * newname instead. If update_relname is true, examine the relname - * fields; otherwise examine the attname fields. - */ -static void -update_ri_trigger_args(Oid relid, - const char *oldname, - const char *newname, - bool fk_scan, - bool update_relname) -{ - Relation tgrel; - ScanKeyData skey[1]; - SysScanDesc trigscan; - HeapTuple tuple; - Datum values[Natts_pg_trigger]; - char nulls[Natts_pg_trigger]; - char replaces[Natts_pg_trigger]; - - tgrel = heap_open(TriggerRelationId, RowExclusiveLock); - if (fk_scan) - { - ScanKeyInit(&skey[0], - Anum_pg_trigger_tgconstrrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(relid)); - trigscan = systable_beginscan(tgrel, TriggerConstrRelidIndexId, - true, SnapshotNow, - 1, skey); - } - else - { - ScanKeyInit(&skey[0], - Anum_pg_trigger_tgrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(relid)); - trigscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, - true, SnapshotNow, - 1, skey); - } - - while ((tuple = systable_getnext(trigscan)) != NULL) - { - Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - bytea *val; - bytea *newtgargs; - bool isnull; - int tg_type; - bool examine_pk; - bool changed; - int tgnargs; - int i; - int newlen; - const char *arga[RI_MAX_ARGUMENTS]; - const char *argp; - - tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid); - if (tg_type == RI_TRIGGER_NONE) - { - /* Not an RI trigger, forget it */ - continue; - } - - /* - * It is an RI trigger, so parse the tgargs bytea. - * - * NB: we assume the field will never be compressed or moved out of - * line; so does trigger.c ... - */ - tgnargs = pg_trigger->tgnargs; - val = DatumGetByteaP(fastgetattr(tuple, - Anum_pg_trigger_tgargs, - tgrel->rd_att, &isnull)); - if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO || - tgnargs > RI_MAX_ARGUMENTS) - { - /* This probably shouldn't happen, but ignore busted triggers */ - continue; - } - argp = (const char *) VARDATA(val); - for (i = 0; i < tgnargs; i++) - { - arga[i] = argp; - argp += strlen(argp) + 1; - } - - /* - * Figure out which item(s) to look at. If the trigger is primary-key - * type and attached to my rel, I should look at the PK fields; if it - * is foreign-key type and attached to my rel, I should look at the FK - * fields. But the opposite rule holds when examining triggers found - * by tgconstrrel search. - */ - examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan); - - changed = false; - if (update_relname) - { - /* Change the relname if needed */ - i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO; - if (strcmp(arga[i], oldname) == 0) - { - arga[i] = newname; - changed = true; - } - } - else - { - /* Change attname(s) if needed */ - i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX : - RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX; - for (; i < tgnargs; i += 2) - { - if (strcmp(arga[i], oldname) == 0) - { - arga[i] = newname; - changed = true; - } - } - } - - if (!changed) - { - /* Don't need to update this tuple */ - continue; - } - - /* - * Construct modified tgargs bytea. - */ - newlen = VARHDRSZ; - for (i = 0; i < tgnargs; i++) - newlen += strlen(arga[i]) + 1; - newtgargs = (bytea *) palloc(newlen); - VARATT_SIZEP(newtgargs) = newlen; - newlen = VARHDRSZ; - for (i = 0; i < tgnargs; i++) - { - strcpy(((char *) newtgargs) + newlen, arga[i]); - newlen += strlen(arga[i]) + 1; - } - - /* - * Build modified tuple. - */ - for (i = 0; i < Natts_pg_trigger; i++) - { - values[i] = (Datum) 0; - replaces[i] = ' '; - nulls[i] = ' '; - } - values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs); - replaces[Anum_pg_trigger_tgargs - 1] = 'r'; - - tuple = heap_modifytuple(tuple, RelationGetDescr(tgrel), values, nulls, replaces); - - /* - * Update pg_trigger and its indexes - */ - simple_heap_update(tgrel, &tuple->t_self, tuple); - - CatalogUpdateIndexes(tgrel, tuple); - - /* - * Invalidate trigger's relation's relcache entry so that other - * backends (and this one too!) are sent SI message to make them - * rebuild relcache entries. (Ideally this should happen - * automatically...) - * - * We can skip this for triggers on relid itself, since that relcache - * flush will happen anyway due to the table or column rename. We - * just need to catch the far ends of RI relationships. - */ - pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); - if (pg_trigger->tgrelid != relid) - CacheInvalidateRelcacheByRelid(pg_trigger->tgrelid); - - /* free up our scratch memory */ - pfree(newtgargs); - heap_freetuple(tuple); - } - - systable_endscan(trigscan); - - heap_close(tgrel, RowExclusiveLock); - - /* - * Increment cmd counter to make updates visible; this is needed in case - * the same tuple has to be updated again by next pass (can happen in case - * of a self-referential FK relationship). - */ - CommandCounterIncrement(); -} - -/* * AlterTable * Execute ALTER TABLE, which can be a list of subcommands * @@ -2552,7 +2319,8 @@ ATRewriteTables(List **wqueue) refrel = heap_open(con->refrelid, RowShareLock); - validateForeignKeyConstraint(fkconstraint, rel, refrel); + validateForeignKeyConstraint(fkconstraint, rel, refrel, + con->conid); heap_close(refrel, NoLock); } @@ -4061,6 +3829,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, Oid pktypoid[INDEX_MAX_KEYS]; Oid fktypoid[INDEX_MAX_KEYS]; Oid opclasses[INDEX_MAX_KEYS]; + Oid pfeqoperators[INDEX_MAX_KEYS]; + Oid ppeqoperators[INDEX_MAX_KEYS]; + Oid ffeqoperators[INDEX_MAX_KEYS]; int i; int numfks, numpks; @@ -4138,6 +3909,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, MemSet(pktypoid, 0, sizeof(pktypoid)); MemSet(fktypoid, 0, sizeof(fktypoid)); MemSet(opclasses, 0, sizeof(opclasses)); + MemSet(pfeqoperators, 0, sizeof(pfeqoperators)); + MemSet(ppeqoperators, 0, sizeof(ppeqoperators)); + MemSet(ffeqoperators, 0, sizeof(ffeqoperators)); numfks = transformColumnNameList(RelationGetRelid(rel), fkconstraint->fk_attrs, @@ -4166,7 +3940,15 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, opclasses); } - /* Be sure referencing and referenced column types are comparable */ + /* + * Look up the equality operators to use in the constraint. + * + * Note that we look for operator(s) with the PK type on the left; when + * the types are different this is the right choice because the PK index + * will need operators with the indexkey on the left. Also, we take the + * PK type as being the declared input type of the opclass, which might be + * binary-compatible but not identical to the PK column type. + */ if (numfks != numpks) ereport(ERROR, (errcode(ERRCODE_INVALID_FOREIGN_KEY), @@ -4174,24 +3956,71 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, for (i = 0; i < numpks; i++) { + 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), @@ -4202,41 +4031,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, format_type_be(fktypoid[i]), format_type_be(pktypoid[i])))); - /* - * Check that the found operator is compatible with the PK index, and - * generate a warning if not, since otherwise costly seqscans will be - * incurred to check FK validity. - */ - if (!op_in_opfamily(oprid(o), get_opclass_family(opclasses[i]))) - ereport(WARNING, - (errmsg("foreign key constraint \"%s\" " - "will require costly sequential scans", - fkconstraint->constr_name), - errdetail("Key columns \"%s\" and \"%s\" " - "are of different types: %s and %s.", - strVal(list_nth(fkconstraint->fk_attrs, i)), - strVal(list_nth(fkconstraint->pk_attrs, i)), - format_type_be(fktypoid[i]), - format_type_be(pktypoid[i])))); - - ReleaseSysCache(o); - } - - /* - * Tell Phase 3 to check that the constraint is satisfied by existing rows - * (we can skip this during table creation). - */ - if (!fkconstraint->skip_validation) - { - NewConstraint *newcon; - - newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); - newcon->name = fkconstraint->constr_name; - newcon->contype = CONSTR_FOREIGN; - newcon->refrelid = RelationGetRelid(pkrel); - newcon->qual = (Node *) fkconstraint; - - tab->constraints = lappend(tab->constraints, newcon); + pfeqoperators[i] = pfeqop; + ppeqoperators[i] = ppeqop; + ffeqoperators[i] = ffeqop; } /* @@ -4254,6 +4051,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * constraint */ RelationGetRelid(pkrel), pkattnum, + pfeqoperators, + ppeqoperators, + ffeqoperators, numpks, fkconstraint->fk_upd_action, fkconstraint->fk_del_action, @@ -4269,6 +4069,24 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, createForeignKeyTriggers(rel, fkconstraint, constrOid); /* + * Tell Phase 3 to check that the constraint is satisfied by existing rows + * (we can skip this during table creation). + */ + if (!fkconstraint->skip_validation) + { + NewConstraint *newcon; + + newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); + newcon->name = fkconstraint->constr_name; + newcon->contype = CONSTR_FOREIGN; + newcon->refrelid = RelationGetRelid(pkrel); + newcon->conid = constrOid; + newcon->qual = (Node *) fkconstraint; + + tab->constraints = lappend(tab->constraints, newcon); + } + + /* * Close pk table, but keep lock until we've committed. */ heap_close(pkrel, NoLock); @@ -4526,25 +4344,15 @@ transformFkeyCheckAttrs(Relation pkrel, static void validateForeignKeyConstraint(FkConstraint *fkconstraint, Relation rel, - Relation pkrel) + Relation pkrel, + Oid constraintOid) { HeapScanDesc scan; HeapTuple tuple; Trigger trig; - ListCell *list; - int count; /* - * See if we can do it with a single LEFT JOIN query. A FALSE result - * indicates we must proceed with the fire-the-trigger method. - */ - if (RI_Initial_Check(fkconstraint, rel, pkrel)) - return; - - /* - * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as - * if that tuple had just been inserted. If any of those fail, it should - * ereport(ERROR) and that's that. + * Build a trigger call structure; we'll need it either way. */ MemSet(&trig, 0, sizeof(trig)); trig.tgoid = InvalidOid; @@ -4552,35 +4360,23 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint, trig.tgenabled = TRUE; trig.tgisconstraint = TRUE; trig.tgconstrrelid = RelationGetRelid(pkrel); + trig.tgconstraint = constraintOid; trig.tgdeferrable = FALSE; trig.tginitdeferred = FALSE; + /* we needn't fill in tgargs */ - trig.tgargs = (char **) palloc(sizeof(char *) * - (4 + list_length(fkconstraint->fk_attrs) - + list_length(fkconstraint->pk_attrs))); - - trig.tgargs[0] = trig.tgname; - trig.tgargs[1] = RelationGetRelationName(rel); - trig.tgargs[2] = RelationGetRelationName(pkrel); - trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype); - count = 4; - foreach(list, fkconstraint->fk_attrs) - { - char *fk_at = strVal(lfirst(list)); - - trig.tgargs[count] = fk_at; - count += 2; - } - count = 5; - foreach(list, fkconstraint->pk_attrs) - { - char *pk_at = strVal(lfirst(list)); - - trig.tgargs[count] = pk_at; - count += 2; - } - trig.tgnargs = count - 1; + /* + * See if we can do it with a single LEFT JOIN query. A FALSE result + * indicates we must proceed with the fire-the-trigger method. + */ + if (RI_Initial_Check(&trig, rel, pkrel)) + return; + /* + * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as + * if that tuple had just been inserted. If any of those fail, it should + * ereport(ERROR) and that's that. + */ scan = heap_beginscan(rel, SnapshotNow, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) @@ -4613,18 +4409,13 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint, } heap_endscan(scan); - - pfree(trig.tgargs); } static void CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint, - ObjectAddress *constrobj, ObjectAddress *trigobj, - bool on_insert) + Oid constraintOid, bool on_insert) { CreateTrigStmt *fk_trigger; - ListCell *fk_attr; - ListCell *pk_attr; fk_trigger = makeNode(CreateTrigStmt); fk_trigger->trigname = fkconstraint->constr_name; @@ -4649,32 +4440,9 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint, fk_trigger->deferrable = fkconstraint->deferrable; fk_trigger->initdeferred = fkconstraint->initdeferred; fk_trigger->constrrel = fkconstraint->pktable; - fk_trigger->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(myRel->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); - if (list_length(fkconstraint->fk_attrs) != list_length(fkconstraint->pk_attrs)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FOREIGN_KEY), - errmsg("number of referencing and referenced columns for foreign key disagree"))); - - forboth(fk_attr, fkconstraint->fk_attrs, - pk_attr, fkconstraint->pk_attrs) - { - fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); - fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); - } - trigobj->objectId = CreateTrigger(fk_trigger, true); - - /* Register dependency from trigger to constraint */ - recordDependencyOn(trigobj, constrobj, DEPENDENCY_INTERNAL); + (void) CreateTrigger(fk_trigger, constraintOid); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -4685,14 +4453,10 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint, */ static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, - Oid constrOid) + Oid constraintOid) { RangeVar *myRel; CreateTrigStmt *fk_trigger; - ListCell *fk_attr; - ListCell *pk_attr; - ObjectAddress trigobj, - constrobj; /* * Reconstruct a RangeVar for my relation (not passed in, unfortunately). @@ -4700,15 +4464,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), pstrdup(RelationGetRelationName(rel))); - /* - * Preset objectAddress fields - */ - constrobj.classId = ConstraintRelationId; - constrobj.objectId = constrOid; - constrobj.objectSubId = 0; - trigobj.classId = TriggerRelationId; - trigobj.objectSubId = 0; - /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -4716,8 +4471,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, * Build and execute a CREATE CONSTRAINT TRIGGER statement for the CHECK * action for both INSERTs and UPDATEs on the referencing table. */ - CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, true); - CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, false); + CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, true); + CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, false); /* * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON @@ -4765,27 +4520,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, (int) fkconstraint->fk_del_action); break; } - fk_trigger->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(myRel->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); - forboth(fk_attr, fkconstraint->fk_attrs, - pk_attr, fkconstraint->pk_attrs) - { - fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); - fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); - } - - trigobj.objectId = CreateTrigger(fk_trigger, true); - /* Register dependency from trigger to constraint */ - recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); + (void) CreateTrigger(fk_trigger, constraintOid); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -4835,49 +4572,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint, (int) fkconstraint->fk_upd_action); break; } - fk_trigger->args = NIL; - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->constr_name)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(myRel->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkconstraint->pktable->relname)); - fk_trigger->args = lappend(fk_trigger->args, - makeString(fkMatchTypeToString(fkconstraint->fk_matchtype))); - forboth(fk_attr, fkconstraint->fk_attrs, - pk_attr, fkconstraint->pk_attrs) - { - fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr)); - fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr)); - } - - trigobj.objectId = CreateTrigger(fk_trigger, true); - /* Register dependency from trigger to constraint */ - recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL); -} - -/* - * fkMatchTypeToString - - * convert FKCONSTR_MATCH_xxx code to string to use in trigger args - */ -static char * -fkMatchTypeToString(char match_type) -{ - switch (match_type) - { - case FKCONSTR_MATCH_FULL: - return pstrdup("FULL"); - case FKCONSTR_MATCH_PARTIAL: - return pstrdup("PARTIAL"); - case FKCONSTR_MATCH_UNSPECIFIED: - return pstrdup("UNSPECIFIED"); - default: - elog(ERROR, "unrecognized match type: %d", - (int) match_type); - } - return NULL; /* can't get here */ + (void) CreateTrigger(fk_trigger, constraintOid); } /* diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index cc7dfc895b..c08525c2e0 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.212 2007/01/25 04:17:46 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.213 2007/02/14 01:58:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,6 +19,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -57,13 +58,12 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, /* * Create a trigger. Returns the OID of the created trigger. * - * forConstraint, if true, says that this trigger is being created to - * implement a constraint. The caller will then be expected to make - * a pg_depend entry linking the trigger to that constraint (and thereby - * to the owning relation(s)). + * constraintOid, if nonzero, says that this trigger is being created to + * implement that constraint. A suitable pg_depend entry will be made + * to link the trigger to that constraint. */ Oid -CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) +CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid) { int16 tgtype; int2vector *tgattr; @@ -91,51 +91,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) rel = heap_openrv(stmt->relation, AccessExclusiveLock); - if (stmt->constrrel != NULL) - constrrelid = RangeVarGetRelid(stmt->constrrel, false); - else if (stmt->isconstraint) - { - /* - * If this trigger is a constraint (and a foreign key one) then we - * really need a constrrelid. Since we don't have one, we'll try to - * generate one from the argument information. - * - * This is really just a workaround for a long-ago pg_dump bug that - * omitted the FROM clause in dumped CREATE CONSTRAINT TRIGGER - * commands. We don't want to bomb out completely here if we can't - * determine the correct relation, because that would prevent loading - * the dump file. Instead, NOTICE here and ERROR in the trigger. - */ - bool needconstrrelid = false; - void *elem = NULL; - - if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check_", 14) == 0) - { - /* A trigger on FK table. */ - needconstrrelid = true; - if (list_length(stmt->args) > RI_PK_RELNAME_ARGNO) - elem = list_nth(stmt->args, RI_PK_RELNAME_ARGNO); - } - else if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_", 8) == 0) - { - /* A trigger on PK table. */ - needconstrrelid = true; - if (list_length(stmt->args) > RI_FK_RELNAME_ARGNO) - elem = list_nth(stmt->args, RI_FK_RELNAME_ARGNO); - } - if (elem != NULL) - { - RangeVar *rel = makeRangeVar(NULL, strVal(elem)); - - constrrelid = RangeVarGetRelid(rel, true); - } - if (needconstrrelid && constrrelid == InvalidOid) - ereport(NOTICE, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("could not determine referenced table for constraint \"%s\"", - stmt->trigname))); - } - if (rel->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -152,15 +107,17 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) if (stmt->isconstraint) { - /* foreign key constraint trigger */ - + /* constraint trigger */ aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_REFERENCES); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel)); - if (constrrelid != InvalidOid) + + if (stmt->constrrel != NULL) { + constrrelid = RangeVarGetRelid(stmt->constrrel, false); + aclresult = pg_class_aclcheck(constrrelid, GetUserId(), ACL_REFERENCES); if (aclresult != ACLCHECK_OK) @@ -170,7 +127,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) } else { - /* real trigger */ + /* regular trigger */ aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRIGGER); if (aclresult != ACLCHECK_OK) @@ -187,23 +144,31 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) trigoid = GetNewOid(tgrel); /* - * If trigger is an RI constraint, use specified trigger name as - * constraint name and build a unique trigger name instead. This is mainly - * for backwards compatibility with CREATE CONSTRAINT TRIGGER commands. + * If trigger is for an RI constraint, the passed-in name is the + * constraint name; save that and build a unique trigger name to avoid + * collisions with user-selected trigger names. */ - if (stmt->isconstraint) + if (OidIsValid(constraintOid)) { snprintf(constrtrigname, sizeof(constrtrigname), "RI_ConstraintTrigger_%u", trigoid); trigname = constrtrigname; constrname = stmt->trigname; } + else if (stmt->isconstraint) + { + /* constraint trigger: trigger name is also constraint name */ + trigname = stmt->trigname; + constrname = stmt->trigname; + } else { + /* regular trigger: use empty constraint name */ trigname = stmt->trigname; constrname = ""; } + /* Compute tgtype */ TRIGGER_CLEAR_TYPE(tgtype); if (stmt->before) TRIGGER_SETT_BEFORE(tgtype); @@ -298,7 +263,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) /* * Build the new pg_trigger tuple. */ - MemSet(nulls, ' ', Natts_pg_trigger * sizeof(char)); + memset(nulls, ' ', Natts_pg_trigger * sizeof(char)); values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein, @@ -310,6 +275,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) values[Anum_pg_trigger_tgconstrname - 1] = DirectFunctionCall1(namein, CStringGetDatum(constrname)); values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid); + values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid); values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable); values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred); @@ -372,10 +338,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) CatalogUpdateIndexes(tgrel, tuple); - myself.classId = TriggerRelationId; - myself.objectId = trigoid; - myself.objectSubId = 0; - heap_freetuple(tuple); heap_close(tgrel, RowExclusiveLock); @@ -412,20 +374,36 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint) /* * Record dependencies for trigger. Always place a normal dependency on - * the function. If we are doing this in response to an explicit CREATE - * TRIGGER command, also make trigger be auto-dropped if its relation is - * dropped or if the FK relation is dropped. (Auto drop is compatible - * with our pre-7.3 behavior.) If the trigger is being made for a - * constraint, we can skip the relation links; the dependency on the - * constraint will indirectly depend on the relations. + * the function. */ + myself.classId = TriggerRelationId; + myself.objectId = trigoid; + myself.objectSubId = 0; + referenced.classId = ProcedureRelationId; referenced.objectId = funcoid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - if (!forConstraint) + if (OidIsValid(constraintOid)) { + /* + * It's for a constraint, so make it an internal dependency of the + * constraint. We can skip depending on the relations, as there'll + * be an indirect dependency via the constraint. + */ + referenced.classId = ConstraintRelationId; + referenced.objectId = constraintOid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL); + } + else + { + /* + * Regular CREATE TRIGGER, so place dependencies. We make trigger be + * auto-dropped if its relation is dropped or if the FK relation is + * dropped. (Auto drop is compatible with our pre-7.3 behavior.) + */ referenced.classId = RelationRelationId; referenced.objectId = RelationGetRelid(rel); referenced.objectSubId = 0; @@ -773,7 +751,7 @@ EnableDisableTrigger(Relation rel, const char *tgname, { Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple); - if (oldtrig->tgisconstraint) + if (OidIsValid(oldtrig->tgconstraint)) { /* system trigger ... ok to process? */ if (skip_system) @@ -886,6 +864,7 @@ RelationBuildTriggers(Relation relation) build->tgenabled = pg_trigger->tgenabled; build->tgisconstraint = pg_trigger->tgisconstraint; build->tgconstrrelid = pg_trigger->tgconstrrelid; + build->tgconstraint = pg_trigger->tgconstraint; build->tgdeferrable = pg_trigger->tgdeferrable; build->tginitdeferred = pg_trigger->tginitdeferred; build->tgnargs = pg_trigger->tgnargs; @@ -1220,6 +1199,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2) return false; if (trig1->tgconstrrelid != trig2->tgconstrrelid) return false; + if (trig1->tgconstraint != trig2->tgconstraint) + return false; if (trig1->tgdeferrable != trig2->tgdeferrable) return false; if (trig1->tginitdeferred != trig2->tginitdeferred) diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 4f88e9561c..99f964db74 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.99 2007/01/05 22:19:26 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.100 2007/02/14 01:58:57 tgl Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -1966,6 +1966,9 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, domainOid, /* domain constraint */ InvalidOid, /* Foreign key fields */ NULL, + NULL, + NULL, + NULL, 0, ' ', ' ', diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 613ef653d1..5cabec2970 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.271 2007/01/23 05:07:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.272 2007/02/14 01:58:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -973,7 +973,7 @@ ProcessUtility(Node *parsetree, break; case T_CreateTrigStmt: - CreateTrigger((CreateTrigStmt *) parsetree, false); + CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid); break; case T_DropPropertyStmt: diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index c21d827d0c..4ba2bb7a98 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -17,7 +17,7 @@ * * 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 $ * * ---------- */ @@ -32,13 +32,17 @@ #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" /* ---------- @@ -46,11 +50,9 @@ * ---------- */ -#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 @@ -72,21 +74,51 @@ #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; @@ -108,10 +140,37 @@ typedef struct RI_QueryHashEntry /* ---------- + * 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; /* ---------- @@ -120,35 +179,41 @@ static HTAB *ri_query_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); @@ -176,8 +241,7 @@ static Datum 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; @@ -186,7 +250,6 @@ RI_FKey_check(PG_FUNCTION_ARGS) RI_QueryKey qkey; void *qplan; int i; - int match_type; /* * Check that this is a valid trigger call on the right time and event. @@ -196,8 +259,9 @@ RI_FKey_check(PG_FUNCTION_ARGS) /* * 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; @@ -237,7 +301,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) * 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 @@ -250,12 +314,9 @@ RI_FKey_check(PG_FUNCTION_ARGS) * 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"); @@ -271,7 +332,8 @@ RI_FKey_check(PG_FUNCTION_ARGS) * ---------- */ 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 */ @@ -287,7 +349,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) 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"); @@ -295,19 +357,14 @@ RI_FKey_check(PG_FUNCTION_ARGS) 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)) { @@ -329,9 +386,9 @@ RI_FKey_check(PG_FUNCTION_ARGS) * 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 @@ -341,12 +398,12 @@ RI_FKey_check(PG_FUNCTION_ARGS) (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 - if ANY column is null, we have a @@ -355,7 +412,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) 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 @@ -387,39 +444,43 @@ RI_FKey_check(PG_FUNCTION_ARGS) */ 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 WHERE pkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY 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); } @@ -431,7 +492,7 @@ RI_FKey_check(PG_FUNCTION_ARGS) 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"); @@ -471,7 +532,7 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS) /* ---------- * 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. * ---------- @@ -479,17 +540,14 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS) 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)) { @@ -506,10 +564,10 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, * 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 /FULL - if ANY column is null, we @@ -517,7 +575,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, */ return true; - case RI_MATCH_TYPE_PARTIAL: + case FKCONSTR_MATCH_PARTIAL: /* * MATCH PARTIAL - all non-null columns must match. (not @@ -548,39 +606,42 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, */ 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 WHERE pkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY 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); } @@ -612,28 +673,29 @@ Datum 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); /* @@ -642,14 +704,11 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) * 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 @@ -659,7 +718,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); } - switch (match_type) + switch (riinfo.confmatchtype) { /* ---------- * SQL3 11.9 @@ -668,12 +727,10 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) * ... 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)) { @@ -704,39 +761,44 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) */ 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 WHERE fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY 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); } @@ -748,7 +810,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) 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"); @@ -760,7 +822,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) /* * 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"))); @@ -770,7 +832,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -787,8 +849,7 @@ Datum 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; @@ -796,20 +857,22 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) 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); /* @@ -819,14 +882,12 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) * 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 @@ -835,12 +896,10 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) * ... 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)) { @@ -865,16 +924,13 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) /* * 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. @@ -893,39 +949,44 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) */ 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 WHERE fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY 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); } @@ -937,7 +998,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) 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"); @@ -949,7 +1010,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) /* * 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"))); @@ -959,7 +1020,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -974,8 +1035,7 @@ Datum 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; @@ -988,13 +1048,16 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ 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); /* @@ -1003,11 +1066,11 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) * 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 @@ -1016,12 +1079,10 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) * ... 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)) { @@ -1051,38 +1112,42 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ 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 WHERE fkatt1 = $1 [AND ...] + * DELETE FROM ONLY 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); } @@ -1095,7 +1160,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) 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"); @@ -1107,7 +1172,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) /* * 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"))); @@ -1117,7 +1182,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1132,8 +1197,7 @@ Datum 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; @@ -1148,13 +1212,16 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ 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); /* @@ -1164,12 +1231,12 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) * 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 @@ -1178,12 +1245,10 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) * ... 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)) { @@ -1208,8 +1273,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* * 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); @@ -1224,11 +1288,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ 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]; @@ -1236,36 +1300,43 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY 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); } @@ -1277,7 +1348,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) 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"); @@ -1289,7 +1360,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* * 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"))); @@ -1299,7 +1370,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1321,8 +1392,7 @@ Datum 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; @@ -1335,13 +1405,16 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) */ 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); /* @@ -1350,11 +1423,11 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) * 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 @@ -1363,12 +1436,10 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) * ... 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)) { @@ -1399,39 +1470,44 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) */ 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 WHERE fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY 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); } @@ -1443,7 +1519,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) 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"); @@ -1455,7 +1531,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) /* * 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"))); @@ -1465,7 +1541,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1487,8 +1563,7 @@ Datum 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; @@ -1502,13 +1577,16 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) */ 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); /* @@ -1518,12 +1596,12 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) * 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 @@ -1532,12 +1610,10 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) * ... 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)) { @@ -1562,8 +1638,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) /* * 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); @@ -1578,39 +1653,44 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) */ 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 WHERE fkatt1 = $1 [AND ...] + * SELECT 1 FROM ONLY 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); } @@ -1622,7 +1702,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) 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"); @@ -1634,7 +1714,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) /* * 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"))); @@ -1644,7 +1724,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1659,8 +1739,7 @@ Datum 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; @@ -1673,13 +1752,16 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) */ 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); /* @@ -1688,11 +1770,11 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) * 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 @@ -1701,12 +1783,10 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) * ... 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)) { @@ -1736,11 +1816,11 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) */ 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]; @@ -1748,35 +1828,40 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY 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); } @@ -1788,7 +1873,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) 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"); @@ -1800,7 +1885,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) /* * 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"))); @@ -1810,7 +1895,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -1825,8 +1910,7 @@ Datum 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; @@ -1834,7 +1918,6 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) RI_QueryKey qkey; void *qplan; int i; - int match_type; bool use_cached_query; /* @@ -1842,13 +1925,16 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) */ 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); /* @@ -1857,13 +1943,12 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * 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 @@ -1872,12 +1957,10 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * ... 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)) { @@ -1902,8 +1985,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* * 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); @@ -1925,9 +2007,9 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * 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 @@ -1936,11 +2018,11 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) 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]; @@ -1948,48 +2030,52 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY 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 - 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); } @@ -2002,7 +2088,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) 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"); @@ -2014,7 +2100,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* * 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"))); @@ -2024,7 +2110,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -2039,8 +2125,7 @@ Datum 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; @@ -2052,13 +2137,16 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) */ 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); /* @@ -2067,11 +2155,11 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) * 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 @@ -2080,12 +2168,10 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) * ... 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)) { @@ -2116,11 +2202,11 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) * 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]; @@ -2129,35 +2215,40 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY 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); } @@ -2169,7 +2260,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) 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"); @@ -2191,7 +2282,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) /* * 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"))); @@ -2201,7 +2292,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -2216,28 +2307,29 @@ Datum 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); /* @@ -2246,14 +2338,12 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * 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 @@ -2262,12 +2352,10 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * ... 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)) { @@ -2292,8 +2380,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* * 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); @@ -2308,11 +2395,11 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) * 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]; @@ -2321,45 +2408,50 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* ---------- * The query string built is * UPDATE ONLY 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 - 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); } @@ -2371,7 +2463,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) 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"); @@ -2393,7 +2485,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* * 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"))); @@ -2403,7 +2495,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* * Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return PointerGetDatum(NULL); } @@ -2420,57 +2512,37 @@ bool 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"))); @@ -2478,7 +2550,7 @@ RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel, } /* Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return false; } @@ -2494,57 +2566,36 @@ bool 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"))); @@ -2552,7 +2603,7 @@ RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel, } /* Never reached */ - elog(ERROR, "invalid match_type"); + elog(ERROR, "invalid confmatchtype"); return false; } @@ -2572,18 +2623,17 @@ RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel, * ---------- */ 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; @@ -2596,11 +2646,13 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * * 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 @@ -2613,50 +2665,57 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * (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 "; @@ -2671,12 +2730,11 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) 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 @@ -2702,10 +2760,11 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * 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 @@ -2728,8 +2787,6 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) { HeapTuple tuple = SPI_tuptable->vals[0]; TupleDesc tupdesc = SPI_tuptable->tupdesc; - int nkeys = list_length(fkconstraint->fk_attrs); - int i; RI_QueryKey qkey; /* @@ -2737,11 +2794,11 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) * 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) @@ -2751,7 +2808,7 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) 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."))); } @@ -2762,12 +2819,12 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel) */ 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); } @@ -2829,90 +2886,80 @@ quoteRelationName(char *buffer, Relation rel) 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]; } } @@ -2923,7 +2970,6 @@ static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind) { TriggerData *trigdata = (TriggerData *) fcinfo->context; - int tgnargs; if (!CALLED_AS_TRIGGER(fcinfo)) ereport(ERROR, @@ -2968,30 +3014,148 @@ ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind) 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); } @@ -3313,52 +3477,30 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname, * 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]; } } @@ -3402,8 +3544,8 @@ ri_NullCheck(Relation rel, HeapTuple tup, RI_QueryKey *key, int pairidx) /* ---------- * 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 @@ -3417,6 +3559,13 @@ ri_InitHashTables(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); } @@ -3486,38 +3635,49 @@ ri_HashPreparedPlan(RI_QueryKey *key, void *plan) */ 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; } @@ -3533,52 +3693,61 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, */ 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. @@ -3586,73 +3755,158 @@ ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup, */ 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. diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3cd317361f..064b8e07f7 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.248 2007/02/03 14:06:54 petere Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.249 2007/02/14 01:58:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -875,30 +875,15 @@ static char * pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, int prettyFlags) { - StringInfoData buf; - Relation conDesc; - SysScanDesc conscan; - ScanKeyData skey[1]; HeapTuple tup; Form_pg_constraint conForm; + StringInfoData buf; - /* - * Fetch the pg_constraint row. There's no syscache for pg_constraint so - * we must do it the hard way. - */ - conDesc = heap_open(ConstraintRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], - ObjectIdAttributeNumber, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(constraintId)); - - conscan = systable_beginscan(conDesc, ConstraintOidIndexId, true, - SnapshotNow, 1, skey); - - tup = systable_getnext(conscan); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "could not find tuple for constraint %u", constraintId); + tup = SearchSysCache(CONSTROID, + ObjectIdGetDatum(constraintId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for constraint %u", constraintId); conForm = (Form_pg_constraint) GETSTRUCT(tup); initStringInfo(&buf); @@ -922,8 +907,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, appendStringInfo(&buf, "FOREIGN KEY ("); /* Fetch and build referencing-column list */ - val = heap_getattr(tup, Anum_pg_constraint_conkey, - RelationGetDescr(conDesc), &isnull); + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conkey, &isnull); if (isnull) elog(ERROR, "null conkey for constraint %u", constraintId); @@ -935,8 +920,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, generate_relation_name(conForm->confrelid)); /* Fetch and build referenced-column list */ - val = heap_getattr(tup, Anum_pg_constraint_confkey, - RelationGetDescr(conDesc), &isnull); + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_confkey, &isnull); if (isnull) elog(ERROR, "null confkey for constraint %u", constraintId); @@ -1038,8 +1023,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, appendStringInfo(&buf, "UNIQUE ("); /* Fetch and build target column list */ - val = heap_getattr(tup, Anum_pg_constraint_conkey, - RelationGetDescr(conDesc), &isnull); + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conkey, &isnull); if (isnull) elog(ERROR, "null conkey for constraint %u", constraintId); @@ -1071,8 +1056,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, List *context; /* Fetch constraint expression in parsetree form */ - val = heap_getattr(tup, Anum_pg_constraint_conbin, - RelationGetDescr(conDesc), &isnull); + val = SysCacheGetAttr(CONSTROID, tup, + Anum_pg_constraint_conbin, &isnull); if (isnull) elog(ERROR, "null conbin for constraint %u", constraintId); @@ -1115,8 +1100,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, } /* Cleanup */ - systable_endscan(conscan); - heap_close(conDesc, AccessShareLock); + ReleaseSysCache(tup); return buf.data; } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 8946cb7315..d6ff883c92 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.147 2007/01/30 01:33:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.148 2007/02/14 01:58:57 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -20,6 +20,7 @@ #include "bootstrap/bootstrap.h" #include "catalog/pg_amop.h" #include "catalog/pg_amproc.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" @@ -897,10 +898,37 @@ get_atttypetypmod(Oid relid, AttrNumber attnum, ReleaseSysCache(tp); } -/* ---------- INDEX CACHE ---------- */ +/* ---------- CONSTRAINT CACHE ---------- */ -/* watch this space... +/* + * get_constraint_name + * Returns the name of a given pg_constraint entry. + * + * Returns a palloc'd copy of the string, or NULL if no such constraint. + * + * NOTE: since constraint name is not unique, be wary of code that uses this + * for anything except preparing error messages. */ +char * +get_constraint_name(Oid conoid) +{ + HeapTuple tp; + + tp = SearchSysCache(CONSTROID, + ObjectIdGetDatum(conoid), + 0, 0, 0); + if (HeapTupleIsValid(tp)) + { + Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(contup->conname)); + ReleaseSysCache(tp); + return result; + } + else + return NULL; +} /* ---------- OPCLASS CACHE ---------- */ diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 5b33ff423e..75f290dda6 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.110 2007/01/05 22:19:43 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.111 2007/02/14 01:58:57 tgl Exp $ * * NOTES * These routines allow the parser/planner/executor to perform @@ -28,6 +28,7 @@ #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" #include "catalog/pg_cast.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" #include "catalog/pg_language.h" @@ -298,7 +299,19 @@ static const struct cachedesc cacheinfo[] = { }, 128 }, - {ConversionRelationId, /* CONOID */ + {ConstraintRelationId, /* CONSTROID */ + ConstraintOidIndexId, + 0, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 1024 + }, + {ConversionRelationId, /* CONVOID */ ConversionOidIndexId, 0, 1, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 2fec7326d5..eef9a4c875 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * by PostgreSQL * * IDENTIFICATION - * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.459 2007/01/25 03:30:43 momjian Exp $ + * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.460 2007/02/14 01:58:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3841,7 +3841,7 @@ getTriggers(TableInfo tblinfo[], int numTables) selectSourceSchema(tbinfo->dobj.namespace->dobj.name); resetPQExpBuffer(query); - if (g_fout->remoteVersion >= 70300) + if (g_fout->remoteVersion >= 80300) { /* * We ignore triggers that are tied to a foreign-key constraint @@ -3855,6 +3855,25 @@ getTriggers(TableInfo tblinfo[], int numTables) "tgconstrrelid::pg_catalog.regclass as tgconstrrelname " "from pg_catalog.pg_trigger t " "where tgrelid = '%u'::pg_catalog.oid " + "and tgconstraint = 0", + tbinfo->dobj.catId.oid); + } + else if (g_fout->remoteVersion >= 70300) + { + /* + * We ignore triggers that are tied to a foreign-key constraint, + * but in these versions we have to grovel through pg_constraint + * to find out + */ + appendPQExpBuffer(query, + "SELECT tgname, " + "tgfoid::pg_catalog.regproc as tgfname, " + "tgtype, tgnargs, tgargs, tgenabled, " + "tgisconstraint, tgconstrname, tgdeferrable, " + "tgconstrrelid, tginitdeferred, tableoid, oid, " + "tgconstrrelid::pg_catalog.regclass as tgconstrrelname " + "from pg_catalog.pg_trigger t " + "where tgrelid = '%u'::pg_catalog.oid " "and (not tgisconstraint " " OR NOT EXISTS" " (SELECT 1 FROM pg_catalog.pg_depend d " diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 6469b6aee4..3e830d9eb9 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.150 2007/01/20 21:17:30 neilc Exp $ + * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.151 2007/02/14 01:58:58 tgl Exp $ */ #include "postgres_fe.h" #include "describe.h" @@ -1128,12 +1128,8 @@ describeOneTableDetails(const char *schemaname, "FROM pg_catalog.pg_trigger t\n" "WHERE t.tgrelid = '%s' " "AND t.tgenabled " - "AND (NOT t.tgisconstraint " - " OR NOT EXISTS" - " (SELECT 1 FROM pg_catalog.pg_depend d " - " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) " - " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))" - " ORDER BY 1", + "AND t.tgconstraint = 0\n" + "ORDER BY 1", oid); result4 = PSQLexec(buf.data, false); if (!result4) @@ -1152,12 +1148,8 @@ describeOneTableDetails(const char *schemaname, "FROM pg_catalog.pg_trigger t\n" "WHERE t.tgrelid = '%s' " "AND NOT t.tgenabled " - "AND (NOT t.tgisconstraint " - " OR NOT EXISTS" - " (SELECT 1 FROM pg_catalog.pg_depend d " - " JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) " - " WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))" - " ORDER BY 1", + "AND t.tgconstraint = 0\n" + "ORDER BY 1", oid); result7 = PSQLexec(buf.data, false); if (!result7) diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 5bdda05960..aec8dee45c 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.383 2007/02/09 03:35:34 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.384 2007/02/14 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200702081 +#define CATALOG_VERSION_NO 200702131 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 73e2cf0cc7..baa650a8ed 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.97 2007/01/05 22:19:52 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.98 2007/02/14 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -214,9 +214,6 @@ DECLARE_UNIQUE_INDEX(pg_tablespace_spcname_index, 2698, on pg_tablespace using b /* This following index is not used for a cache and is not unique */ DECLARE_INDEX(pg_trigger_tgconstrname_index, 2699, on pg_trigger using btree(tgconstrname name_ops)); #define TriggerConstrNameIndexId 2699 -/* This following index is not used for a cache and is not unique */ -DECLARE_INDEX(pg_trigger_tgconstrrelid_index, 2700, on pg_trigger using btree(tgconstrrelid oid_ops)); -#define TriggerConstrRelidIndexId 2700 DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using btree(tgrelid oid_ops, tgname name_ops)); #define TriggerRelidNameIndexId 2701 DECLARE_UNIQUE_INDEX(pg_trigger_oid_index, 2702, on pg_trigger using btree(oid oid_ops)); diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 6fbfb6a0cd..d01c22c703 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.24 2007/01/05 22:19:52 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.25 2007/02/14 01:58:58 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -92,6 +92,24 @@ CATALOG(pg_constraint,2606) int2 confkey[1]; /* + * If a foreign key, the OIDs of the PK = FK equality operators for each + * column of the constraint + */ + Oid conpfeqop[1]; + + /* + * If a foreign key, the OIDs of the PK = PK equality operators for each + * column of the constraint (i.e., equality for the referenced columns) + */ + Oid conppeqop[1]; + + /* + * If a foreign key, the OIDs of the FK = FK equality operators for each + * column of the constraint (i.e., equality for the referencing columns) + */ + Oid conffeqop[1]; + + /* * If a check constraint, nodeToString representation of expression */ text conbin; @@ -113,7 +131,7 @@ typedef FormData_pg_constraint *Form_pg_constraint; * compiler constants for pg_constraint * ---------------- */ -#define Natts_pg_constraint 15 +#define Natts_pg_constraint 18 #define Anum_pg_constraint_conname 1 #define Anum_pg_constraint_connamespace 2 #define Anum_pg_constraint_contype 3 @@ -127,8 +145,11 @@ typedef FormData_pg_constraint *Form_pg_constraint; #define Anum_pg_constraint_confmatchtype 11 #define Anum_pg_constraint_conkey 12 #define Anum_pg_constraint_confkey 13 -#define Anum_pg_constraint_conbin 14 -#define Anum_pg_constraint_consrc 15 +#define Anum_pg_constraint_conpfeqop 14 +#define Anum_pg_constraint_conppeqop 15 +#define Anum_pg_constraint_conffeqop 16 +#define Anum_pg_constraint_conbin 17 +#define Anum_pg_constraint_consrc 18 /* Valid values for contype */ @@ -167,6 +188,9 @@ extern Oid CreateConstraintEntry(const char *constraintName, Oid domainId, Oid foreignRelId, const int16 *foreignKey, + const Oid *pfEqOp, + const Oid *ppEqOp, + const Oid *ffEqOp, int foreignNKeys, char foreignUpdateType, char foreignDeleteType, @@ -184,8 +208,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2, const char *label, Oid namespace, List *others); -extern char *GetConstraintNameForTrigger(Oid triggerId); - extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, Oid newNspId, bool isType); diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h index b5408253b3..df22089e31 100644 --- a/src/include/catalog/pg_trigger.h +++ b/src/include/catalog/pg_trigger.h @@ -1,11 +1,14 @@ /*------------------------------------------------------------------------- * * pg_trigger.h + * definition of the system "trigger" relation (pg_trigger) + * along with the relation's initial contents. * * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California - * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.26 2007/01/05 22:19:53 momjian Exp $ + * + * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.27 2007/02/14 01:58:58 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -26,23 +29,30 @@ /* ---------------- * pg_trigger definition. cpp turns this into * typedef struct FormData_pg_trigger + * + * Note: when tgconstraint is nonzero, tgisconstraint must be true, and + * tgconstrname, tgconstrrelid, tgdeferrable, tginitdeferred are redundant + * with the referenced pg_constraint entry. The reason we keep these fields + * is that we support "stand-alone" constraint triggers with no corresponding + * pg_constraint entry. * ---------------- */ #define TriggerRelationId 2620 CATALOG(pg_trigger,2620) { - Oid tgrelid; /* triggered relation */ - NameData tgname; /* trigger' name */ + Oid tgrelid; /* relation trigger is attached to */ + NameData tgname; /* trigger's name */ Oid tgfoid; /* OID of function to be called */ int2 tgtype; /* BEFORE/AFTER UPDATE/DELETE/INSERT - * ROW/STATEMENT */ + * ROW/STATEMENT; see below */ bool tgenabled; /* trigger is enabled/disabled */ - bool tgisconstraint; /* trigger is a RI constraint */ - NameData tgconstrname; /* RI constraint name */ - Oid tgconstrrelid; /* RI table of foreign key definition */ - bool tgdeferrable; /* RI trigger is deferrable */ - bool tginitdeferred; /* RI trigger is deferred initially */ + bool tgisconstraint; /* trigger is a constraint trigger */ + NameData tgconstrname; /* constraint name */ + Oid tgconstrrelid; /* constraint's FROM table, if any */ + Oid tgconstraint; /* owning pg_constraint entry, if any */ + bool tgdeferrable; /* constraint trigger is deferrable */ + bool tginitdeferred; /* constraint trigger is deferred initially */ int2 tgnargs; /* # of extra arguments in tgargs */ /* VARIABLE LENGTH FIELDS: */ @@ -61,7 +71,7 @@ typedef FormData_pg_trigger *Form_pg_trigger; * compiler constants for pg_trigger * ---------------- */ -#define Natts_pg_trigger 13 +#define Natts_pg_trigger 14 #define Anum_pg_trigger_tgrelid 1 #define Anum_pg_trigger_tgname 2 #define Anum_pg_trigger_tgfoid 3 @@ -70,18 +80,21 @@ typedef FormData_pg_trigger *Form_pg_trigger; #define Anum_pg_trigger_tgisconstraint 6 #define Anum_pg_trigger_tgconstrname 7 #define Anum_pg_trigger_tgconstrrelid 8 -#define Anum_pg_trigger_tgdeferrable 9 -#define Anum_pg_trigger_tginitdeferred 10 -#define Anum_pg_trigger_tgnargs 11 -#define Anum_pg_trigger_tgattr 12 -#define Anum_pg_trigger_tgargs 13 +#define Anum_pg_trigger_tgconstraint 9 +#define Anum_pg_trigger_tgdeferrable 10 +#define Anum_pg_trigger_tginitdeferred 11 +#define Anum_pg_trigger_tgnargs 12 +#define Anum_pg_trigger_tgattr 13 +#define Anum_pg_trigger_tgargs 14 +/* Bits within tgtype */ #define TRIGGER_TYPE_ROW (1 << 0) #define TRIGGER_TYPE_BEFORE (1 << 1) #define TRIGGER_TYPE_INSERT (1 << 2) #define TRIGGER_TYPE_DELETE (1 << 3) #define TRIGGER_TYPE_UPDATE (1 << 4) +/* Macros for manipulating tgtype */ #define TRIGGER_CLEAR_TYPE(type) ((type) = 0) #define TRIGGER_SETT_ROW(type) ((type) |= TRIGGER_TYPE_ROW) diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 284815b47f..ac9eb7296d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.60 2007/01/05 22:19:54 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.61 2007/02/14 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -78,34 +78,8 @@ typedef struct TriggerData #define TRIGGER_FIRED_AFTER(event) \ (!TRIGGER_FIRED_BEFORE (event)) -/* - * RI trigger function arguments are stored in pg_trigger.tgargs bytea - * - * constrname\0fkrel\0pkrel\0matchtype\0fkatt\0pkatt\0fkatt\0pkatt\0... - * - * There are one or more pairs of fkatt/pkatt names. - * - * The relation names are no longer of much use since they are not - * guaranteed unique; they are present only for backwards compatibility. - * Use the tgrelid and tgconstrrelid fields to identify the referenced - * relations, instead. (But note that which is which will depend on which - * trigger you are looking at!) - */ -#define RI_CONSTRAINT_NAME_ARGNO 0 -#define RI_FK_RELNAME_ARGNO 1 -#define RI_PK_RELNAME_ARGNO 2 -#define RI_MATCH_TYPE_ARGNO 3 -#define RI_FIRST_ATTNAME_ARGNO 4 /* first attname pair starts - * here */ - -#define RI_KEYPAIR_FK_IDX 0 -#define RI_KEYPAIR_PK_IDX 1 - -#define RI_MAX_NUMKEYS INDEX_MAX_KEYS -#define RI_MAX_ARGUMENTS (RI_FIRST_ATTNAME_ARGNO + (RI_MAX_NUMKEYS * 2)) - -extern Oid CreateTrigger(CreateTrigStmt *stmt, bool forConstraint); +extern Oid CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid); extern void DropTrigger(Oid relid, const char *trigname, DropBehavior behavior, bool missing_ok); @@ -175,10 +149,10 @@ extern bool RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel, HeapTuple old_row, HeapTuple new_row); extern bool RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel, HeapTuple old_row, HeapTuple new_row); -extern bool RI_Initial_Check(FkConstraint *fkconstraint, - Relation rel, - Relation pkrel); +extern bool RI_Initial_Check(Trigger *trigger, + Relation fk_rel, Relation pk_rel); +/* result values for RI_FKey_trigger_type: */ #define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */ #define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */ #define RI_TRIGGER_NONE 0 /* is not an RI trigger function */ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 8e9bfaa1ed..78c45891fa 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.116 2007/01/30 01:33:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.117 2007/02/14 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -58,6 +58,7 @@ extern Oid get_atttype(Oid relid, AttrNumber attnum); extern int32 get_atttypmod(Oid relid, AttrNumber attnum); extern void get_atttypetypmod(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod); +extern char *get_constraint_name(Oid conoid); extern Oid get_opclass_family(Oid opclass); extern Oid get_opclass_input_type(Oid opclass); extern RegProcedure get_opcode(Oid opno); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 8ce7e118a4..3ac44b11cd 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.96 2007/01/25 02:17:26 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.97 2007/02/14 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,6 +56,7 @@ typedef struct Trigger bool tgenabled; bool tgisconstraint; Oid tgconstrrelid; + Oid tgconstraint; bool tgdeferrable; bool tginitdeferred; int16 tgnargs; diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index b8e55e57ef..c6967251ce 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.67 2007/01/05 22:19:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.68 2007/02/14 01:58:58 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,42 +28,43 @@ * Keep them in alphabetical order. */ -#define AGGFNOID 0 -#define AMNAME 1 -#define AMOID 2 -#define AMOPOPID 3 -#define AMOPSTRATEGY 4 -#define AMPROCNUM 5 -#define ATTNAME 6 -#define ATTNUM 7 -#define AUTHMEMMEMROLE 8 -#define AUTHMEMROLEMEM 9 -#define AUTHNAME 10 -#define AUTHOID 11 -#define CASTSOURCETARGET 12 -#define CLAAMNAMENSP 13 -#define CLAOID 14 -#define CONDEFAULT 15 -#define CONNAMENSP 16 -#define CONOID 17 -#define DATABASEOID 18 -#define INDEXRELID 19 -#define LANGNAME 20 -#define LANGOID 21 -#define NAMESPACENAME 22 -#define NAMESPACEOID 23 -#define OPERNAMENSP 24 -#define OPEROID 25 -#define OPFAMILYAMNAMENSP 26 -#define OPFAMILYOID 27 -#define PROCNAMEARGSNSP 28 -#define PROCOID 29 -#define RELNAMENSP 30 -#define RELOID 31 -#define RULERELNAME 32 -#define STATRELATT 33 -#define TYPENAMENSP 34 -#define TYPEOID 35 +#define AGGFNOID 0 +#define AMNAME 1 +#define AMOID 2 +#define AMOPOPID 3 +#define AMOPSTRATEGY 4 +#define AMPROCNUM 5 +#define ATTNAME 6 +#define ATTNUM 7 +#define AUTHMEMMEMROLE 8 +#define AUTHMEMROLEMEM 9 +#define AUTHNAME 10 +#define AUTHOID 11 +#define CASTSOURCETARGET 12 +#define CLAAMNAMENSP 13 +#define CLAOID 14 +#define CONDEFAULT 15 +#define CONNAMENSP 16 +#define CONSTROID 17 +#define CONVOID 18 +#define DATABASEOID 19 +#define INDEXRELID 20 +#define LANGNAME 21 +#define LANGOID 22 +#define NAMESPACENAME 23 +#define NAMESPACEOID 24 +#define OPERNAMENSP 25 +#define OPEROID 26 +#define OPFAMILYAMNAMENSP 27 +#define OPFAMILYOID 28 +#define PROCNAMEARGSNSP 29 +#define PROCOID 30 +#define RELNAMENSP 31 +#define RELOID 32 +#define RULERELNAME 33 +#define STATRELATT 34 +#define TYPENAMENSP 35 +#define TYPEOID 36 extern void InitCatalogCache(void); extern void InitCatalogCachePhase2(void); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index a1a61f710c..11b8c24c3d 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -195,8 +195,9 @@ DROP TABLE tmp2; -- is run in parallel with foreign_key.sql. CREATE TEMP TABLE PKTABLE (ptest1 int PRIMARY KEY); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable" +INSERT INTO PKTABLE VALUES(42); CREATE TEMP TABLE FKTABLE (ftest1 inet); --- This next should fail, because inet=int does not exist +-- This next should fail, because int=inet does not exist ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer. @@ -205,21 +206,40 @@ DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and i ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1); ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer. --- This should succeed, even though they are different types --- because varchar=int does exist DROP TABLE FKTABLE; -CREATE TEMP TABLE FKTABLE (ftest1 varchar); +-- This should succeed, even though they are different types, +-- because int=int8 exists and is a member of the integer opfamily +CREATE TEMP TABLE FKTABLE (ftest1 int8); ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; -WARNING: foreign key constraint "fktable_ftest1_fkey" will require costly sequential scans -DETAIL: Key columns "ftest1" and "ptest1" are of different types: character varying and integer. --- As should this -ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1); -WARNING: foreign key constraint "fktable_ftest1_fkey1" will require costly sequential scans -DETAIL: Key columns "ftest1" and "ptest1" are of different types: character varying and integer. -DROP TABLE pktable cascade; -NOTICE: drop cascades to constraint fktable_ftest1_fkey1 on table fktable -NOTICE: drop cascades to constraint fktable_ftest1_fkey on table fktable -DROP TABLE fktable; +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey" +DETAIL: Key (ftest1)=(43) is not present in table "pktable". +DROP TABLE FKTABLE; +-- This should fail, because we'd have to cast numeric to int which is +-- not an implicit coercion (or use numeric=numeric, but that's not part +-- of the integer opfamily) +CREATE TEMP TABLE FKTABLE (ftest1 numeric); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented +DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer. +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; +-- On the other hand, this should work because int implicitly promotes to +-- numeric, and we allow promotion on the FK side +CREATE TEMP TABLE PKTABLE (ptest1 numeric PRIMARY KEY); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable" +INSERT INTO PKTABLE VALUES(42); +CREATE TEMP TABLE FKTABLE (ftest1 int); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey" +DETAIL: Key (ftest1)=(43) is not present in table "pktable". +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet, PRIMARY KEY(ptest1, ptest2)); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable" diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 5424731d4d..41c2f39788 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -646,7 +646,7 @@ SELECT * from FKTABLE; UPDATE PKTABLE set ptest2=5 where ptest2=2; ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3" DETAIL: Key (ftest1,ftest2,ftest3)=(1,-1,3) is not present in table "pktable". -CONTEXT: SQL statement "UPDATE ONLY "public"."fktable" SET "ftest2" = DEFAULT WHERE "ftest1" = $1 AND "ftest2" = $2 AND "ftest3" = $3" +CONTEXT: SQL statement "UPDATE ONLY "public"."fktable" SET "ftest2" = DEFAULT WHERE $1 OPERATOR(pg_catalog.=) "ftest1" AND $2 OPERATOR(pg_catalog.=) "ftest2" AND $3 OPERATOR(pg_catalog.=) "ftest3"" -- Try to update something that will set default UPDATE PKTABLE set ptest1=0, ptest2=5, ptest3=10 where ptest2=2; UPDATE PKTABLE set ptest2=10 where ptest2=4; @@ -749,7 +749,8 @@ DROP TABLE PKTABLE; -- Basic one column, two table setup CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable" --- This next should fail, because inet=int does not exist +INSERT INTO PKTABLE VALUES(42); +-- This next should fail, because int=inet does not exist CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable); ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer. @@ -758,16 +759,41 @@ DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and i CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1)); ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer. --- This should succeed (with a warning), even though they are different types --- because int=varchar does exist -CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable); -WARNING: foreign key constraint "fktable_ftest1_fkey" will require costly sequential scans -DETAIL: Key columns "ftest1" and "ptest1" are of different types: character varying and integer. +-- This should succeed, even though they are different types, +-- because int=int8 exists and is a member of the integer opfamily +CREATE TABLE FKTABLE (ftest1 int8 REFERENCES pktable); +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey" +DETAIL: Key (ftest1)=(43) is not present in table "pktable". +UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed +UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey" +DETAIL: Key (ftest1)=(43) is not present in table "pktable". DROP TABLE FKTABLE; --- As should this -CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable(ptest1)); -WARNING: foreign key constraint "fktable_ftest1_fkey" will require costly sequential scans -DETAIL: Key columns "ftest1" and "ptest1" are of different types: character varying and integer. +-- This should fail, because we'd have to cast numeric to int which is +-- not an implicit coercion (or use numeric=numeric, but that's not part +-- of the integer opfamily) +CREATE TABLE FKTABLE (ftest1 numeric REFERENCES pktable); +ERROR: foreign key constraint "fktable_ftest1_fkey" cannot be implemented +DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer. +DROP TABLE PKTABLE; +-- On the other hand, this should work because int implicitly promotes to +-- numeric, and we allow promotion on the FK side +CREATE TABLE PKTABLE (ptest1 numeric PRIMARY KEY); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable" +INSERT INTO PKTABLE VALUES(42); +CREATE TABLE FKTABLE (ftest1 int REFERENCES pktable); +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey" +DETAIL: Key (ftest1)=(43) is not present in table "pktable". +UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed +UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey" +DETAIL: Key (ftest1)=(43) is not present in table "pktable". DROP TABLE FKTABLE; DROP TABLE PKTABLE; -- Two columns, two tables @@ -1083,21 +1109,24 @@ CREATE TEMP TABLE fktable ( x5 INT2 ); -- check individual constraints with alter table. --- should generate warnings +-- should fail +-- varchar does not promote to real ALTER TABLE fktable ADD CONSTRAINT fk_2_3 FOREIGN KEY (x2) REFERENCES pktable(id3); -WARNING: foreign key constraint "fk_2_3" will require costly sequential scans -DETAIL: Key columns "x2" and "id3" are of different types: character varying and real. +ERROR: foreign key constraint "fk_2_3" cannot be implemented +DETAIL: Key columns "x2" and "id3" are of incompatible types: character varying and real. +-- nor to int4 ALTER TABLE fktable ADD CONSTRAINT fk_2_1 FOREIGN KEY (x2) REFERENCES pktable(id1); -WARNING: foreign key constraint "fk_2_1" will require costly sequential scans -DETAIL: Key columns "x2" and "id1" are of different types: character varying and integer. +ERROR: foreign key constraint "fk_2_1" cannot be implemented +DETAIL: Key columns "x2" and "id1" are of incompatible types: character varying and integer. +-- real does not promote to int4 ALTER TABLE fktable ADD CONSTRAINT fk_3_1 FOREIGN KEY (x3) REFERENCES pktable(id1); -WARNING: foreign key constraint "fk_3_1" will require costly sequential scans -DETAIL: Key columns "x3" and "id1" are of different types: real and integer. --- should NOT generate warnings --- int4 promotes to text, so this is ok +ERROR: foreign key constraint "fk_3_1" cannot be implemented +DETAIL: Key columns "x3" and "id1" are of incompatible types: real and integer. +-- should succeed +-- int4 promotes to text, so this is allowed (though pretty durn debatable) ALTER TABLE fktable ADD CONSTRAINT fk_1_2 FOREIGN KEY (x1) REFERENCES pktable(id2); -- int4 promotes to real @@ -1106,45 +1135,36 @@ FOREIGN KEY (x1) REFERENCES pktable(id3); -- text is compatible with varchar ALTER TABLE fktable ADD CONSTRAINT fk_4_2 FOREIGN KEY (x4) REFERENCES pktable(id2); --- int2 is part of int4 opclass as of 8.0 +-- int2 is part of integer opfamily as of 8.0 ALTER TABLE fktable ADD CONSTRAINT fk_5_1 FOREIGN KEY (x5) REFERENCES pktable(id1); -- check multikey cases, especially out-of-order column lists --- no warnings here +-- these should work ALTER TABLE fktable ADD CONSTRAINT fk_123_123 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id1,id2,id3); ALTER TABLE fktable ADD CONSTRAINT fk_213_213 FOREIGN KEY (x2,x1,x3) REFERENCES pktable(id2,id1,id3); ALTER TABLE fktable ADD CONSTRAINT fk_253_213 FOREIGN KEY (x2,x5,x3) REFERENCES pktable(id2,id1,id3); --- warnings here +-- these should fail ALTER TABLE fktable ADD CONSTRAINT fk_123_231 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1); -WARNING: foreign key constraint "fk_123_231" will require costly sequential scans -DETAIL: Key columns "x2" and "id3" are of different types: character varying and real. -WARNING: foreign key constraint "fk_123_231" will require costly sequential scans -DETAIL: Key columns "x3" and "id1" are of different types: real and integer. +ERROR: foreign key constraint "fk_123_231" cannot be implemented +DETAIL: Key columns "x2" and "id3" are of incompatible types: character varying and real. ALTER TABLE fktable ADD CONSTRAINT fk_241_132 FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2); -WARNING: foreign key constraint "fk_241_132" will require costly sequential scans -DETAIL: Key columns "x2" and "id1" are of different types: character varying and integer. -WARNING: foreign key constraint "fk_241_132" will require costly sequential scans -DETAIL: Key columns "x4" and "id3" are of different types: text and real. +ERROR: foreign key constraint "fk_241_132" cannot be implemented +DETAIL: Key columns "x2" and "id1" are of incompatible types: character varying and integer. DROP TABLE pktable, fktable CASCADE; -NOTICE: drop cascades to constraint fk_241_132 on table fktable -NOTICE: drop cascades to constraint fk_123_231 on table fktable NOTICE: drop cascades to constraint fk_253_213 on table fktable NOTICE: drop cascades to constraint fk_213_213 on table fktable NOTICE: drop cascades to constraint fk_123_123 on table fktable NOTICE: drop cascades to constraint fk_5_1 on table fktable -NOTICE: drop cascades to constraint fk_3_1 on table fktable -NOTICE: drop cascades to constraint fk_2_1 on table fktable NOTICE: drop cascades to constraint fktable_x1_fkey on table fktable NOTICE: drop cascades to constraint fk_4_2 on table fktable NOTICE: drop cascades to constraint fk_1_2 on table fktable NOTICE: drop cascades to constraint fktable_x2_fkey on table fktable NOTICE: drop cascades to constraint fk_1_3 on table fktable -NOTICE: drop cascades to constraint fk_2_3 on table fktable NOTICE: drop cascades to constraint fktable_x3_fkey on table fktable -- test a tricky case: we can elide firing the FK check trigger during -- an UPDATE if the UPDATE did not change the foreign key diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 2ed67b3a1c..d52d6c822e 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -241,21 +241,40 @@ DROP TABLE tmp2; -- is run in parallel with foreign_key.sql. CREATE TEMP TABLE PKTABLE (ptest1 int PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); CREATE TEMP TABLE FKTABLE (ftest1 inet); --- This next should fail, because inet=int does not exist +-- This next should fail, because int=inet does not exist ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; -- This should also fail for the same reason, but here we -- give the column name ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1); --- This should succeed, even though they are different types --- because varchar=int does exist DROP TABLE FKTABLE; -CREATE TEMP TABLE FKTABLE (ftest1 varchar); +-- This should succeed, even though they are different types, +-- because int=int8 exists and is a member of the integer opfamily +CREATE TEMP TABLE FKTABLE (ftest1 int8); ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; --- As should this -ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1); -DROP TABLE pktable cascade; -DROP TABLE fktable; +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +DROP TABLE FKTABLE; +-- This should fail, because we'd have to cast numeric to int which is +-- not an implicit coercion (or use numeric=numeric, but that's not part +-- of the integer opfamily) +CREATE TEMP TABLE FKTABLE (ftest1 numeric); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; +-- On the other hand, this should work because int implicitly promotes to +-- numeric, and we allow promotion on the FK side +CREATE TEMP TABLE PKTABLE (ptest1 numeric PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); +CREATE TEMP TABLE FKTABLE (ftest1 int); +ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable; +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +DROP TABLE FKTABLE; +DROP TABLE PKTABLE; CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet, PRIMARY KEY(ptest1, ptest2)); diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 2b22d0cecd..16eee1e754 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -444,17 +444,36 @@ DROP TABLE PKTABLE; -- -- Basic one column, two table setup CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); --- This next should fail, because inet=int does not exist +INSERT INTO PKTABLE VALUES(42); +-- This next should fail, because int=inet does not exist CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable); -- This should also fail for the same reason, but here we -- give the column name CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1)); --- This should succeed (with a warning), even though they are different types --- because int=varchar does exist -CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable); +-- This should succeed, even though they are different types, +-- because int=int8 exists and is a member of the integer opfamily +CREATE TABLE FKTABLE (ftest1 int8 REFERENCES pktable); +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed +UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail DROP TABLE FKTABLE; --- As should this -CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable(ptest1)); +-- This should fail, because we'd have to cast numeric to int which is +-- not an implicit coercion (or use numeric=numeric, but that's not part +-- of the integer opfamily) +CREATE TABLE FKTABLE (ftest1 numeric REFERENCES pktable); +DROP TABLE PKTABLE; +-- On the other hand, this should work because int implicitly promotes to +-- numeric, and we allow promotion on the FK side +CREATE TABLE PKTABLE (ptest1 numeric PRIMARY KEY); +INSERT INTO PKTABLE VALUES(42); +CREATE TABLE FKTABLE (ftest1 int REFERENCES pktable); +-- Check it actually works +INSERT INTO FKTABLE VALUES(42); -- should succeed +INSERT INTO FKTABLE VALUES(43); -- should fail +UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed +UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail DROP TABLE FKTABLE; DROP TABLE PKTABLE; @@ -727,20 +746,23 @@ CREATE TEMP TABLE fktable ( -- check individual constraints with alter table. --- should generate warnings +-- should fail +-- varchar does not promote to real ALTER TABLE fktable ADD CONSTRAINT fk_2_3 FOREIGN KEY (x2) REFERENCES pktable(id3); +-- nor to int4 ALTER TABLE fktable ADD CONSTRAINT fk_2_1 FOREIGN KEY (x2) REFERENCES pktable(id1); +-- real does not promote to int4 ALTER TABLE fktable ADD CONSTRAINT fk_3_1 FOREIGN KEY (x3) REFERENCES pktable(id1); --- should NOT generate warnings +-- should succeed --- int4 promotes to text, so this is ok +-- int4 promotes to text, so this is allowed (though pretty durn debatable) ALTER TABLE fktable ADD CONSTRAINT fk_1_2 FOREIGN KEY (x1) REFERENCES pktable(id2); @@ -752,13 +774,13 @@ FOREIGN KEY (x1) REFERENCES pktable(id3); ALTER TABLE fktable ADD CONSTRAINT fk_4_2 FOREIGN KEY (x4) REFERENCES pktable(id2); --- int2 is part of int4 opclass as of 8.0 +-- int2 is part of integer opfamily as of 8.0 ALTER TABLE fktable ADD CONSTRAINT fk_5_1 FOREIGN KEY (x5) REFERENCES pktable(id1); -- check multikey cases, especially out-of-order column lists --- no warnings here +-- these should work ALTER TABLE fktable ADD CONSTRAINT fk_123_123 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id1,id2,id3); @@ -769,7 +791,7 @@ FOREIGN KEY (x2,x1,x3) REFERENCES pktable(id2,id1,id3); ALTER TABLE fktable ADD CONSTRAINT fk_253_213 FOREIGN KEY (x2,x5,x3) REFERENCES pktable(id2,id1,id3); --- warnings here +-- these should fail ALTER TABLE fktable ADD CONSTRAINT fk_123_231 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1); -- 2.11.0