Fix up foreign-key mechanism so that there is a sound semantic basis for the
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Feb 2007 01:58:58 +0000 (01:58 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Feb 2007 01:58:58 +0000 (01:58 +0000)
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.

33 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/trigger.sgml
src/backend/catalog/aclchk.c
src/backend/catalog/dependency.c
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/catalog/namespace.c
src/backend/catalog/pg_constraint.c
src/backend/catalog/pg_conversion.c
src/backend/commands/conversioncmds.c
src/backend/commands/explain.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/commands/typecmds.c
src/backend/tcop/utility.c
src/backend/utils/adt/ri_triggers.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/lsyscache.c
src/backend/utils/cache/syscache.c
src/bin/pg_dump/pg_dump.c
src/bin/psql/describe.c
src/include/catalog/catversion.h
src/include/catalog/indexing.h
src/include/catalog/pg_constraint.h
src/include/catalog/pg_trigger.h
src/include/commands/trigger.h
src/include/utils/lsyscache.h
src/include/utils/rel.h
src/include/utils/syscache.h
src/test/regress/expected/alter_table.out
src/test/regress/expected/foreign_key.out
src/test/regress/sql/alter_table.sql
src/test/regress/sql/foreign_key.sql

index f5e15f1985af088e5484a3ad03eb90baf4a33702..9a9f9b55bef5378e34f297aae5fde5b4b5e4c03e 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.144 2007/01/31 20:56:16 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.145 2007/02/14 01:58:55 tgl Exp $ -->
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
  -->
       <entry>If a foreign key, list of the referenced columns</entry>
      </row>
 
+     <row>
+      <entry><structfield>conpfeqop</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
+      <entry>If a foreign key, list of the equality operators for PK = FK comparisons</entry>
+     </row>
+
+     <row>
+      <entry><structfield>conppeqop</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
+      <entry>If a foreign key, list of the equality operators for PK = PK comparisons</entry>
+     </row>
+
+     <row>
+      <entry><structfield>conffeqop</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
+      <entry>If a foreign key, list of the equality operators for FK = FK comparisons</entry>
+     </row>
+
      <row>
       <entry><structfield>conbin</structfield></entry>
       <entry><type>text</type></entry>
   <note>
    <para>
     <literal>pg_class.relchecks</literal> 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.
    </para>
   </note>
 
       <entry><structfield>tgisconstraint</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>True if trigger implements a referential integrity constraint</entry>
+      <entry>True if trigger is a <quote>constraint trigger</></entry>
      </row>
 
      <row>
       <entry><structfield>tgconstrname</structfield></entry>
       <entry><type>name</type></entry>
       <entry></entry>
-      <entry>Referential integrity constraint name</entry>
+      <entry>Constraint name, if a constraint trigger</entry>
      </row>
 
      <row>
       <entry><structfield>tgconstrrelid</structfield></entry>
       <entry><type>oid</type></entry>
       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
-      <entry>The table referenced by an referential integrity constraint</entry>
+      <entry>The table referenced by a referential integrity constraint</entry>
+     </row>
+
+     <row>
+      <entry><structfield>tgconstraint</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-constraint"><structname>pg_constraint</structname></link>.oid</literal></entry>
+      <entry>The <structname>pg_constraint</> entry owning the trigger, if any</entry>
      </row>
 
      <row>
       <entry><structfield>tgdeferrable</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>True if deferrable</entry>
+      <entry>True if constraint trigger is deferrable</entry>
      </row>
 
      <row>
       <entry><structfield>tginitdeferred</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>True if initially deferred</entry>
+      <entry>True if constraint trigger is initially deferred</entry>
      </row>
 
      <row>
    </tgroup>
   </table>
 
+  <note>
+   <para>
+    When <structfield>tgconstraint</> is nonzero,
+    <structfield>tgisconstraint</> must be true, and
+    <structfield>tgconstrname</>, <structfield>tgconstrrelid</>,
+    <structfield>tgdeferrable</>, <structfield>tginitdeferred</> are redundant
+    with the referenced <structname>pg_constraint</> entry.  The reason we
+    keep these fields is that we support <quote>stand-alone</> constraint
+    triggers with no corresponding <structname>pg_constraint</> entry.
+   </para>
+  </note>
+
   <note>
    <para>
     <literal>pg_class.reltriggers</literal> 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.
    </para>
   </note>
 
index 0479f57b0d2f94b3964376e7b7501d17753da429..193b8173974d6b412be80d44ff51910c639a6709 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.49 2007/02/01 00:28:18 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.50 2007/02/14 01:58:56 tgl Exp $ -->
 
  <chapter id="triggers">
   <title>Triggers</title>
@@ -467,6 +467,7 @@ typedef struct Trigger
     bool        tgenabled;
     bool        tgisconstraint;
     Oid         tgconstrrelid;
+    Oid         tgconstraint;
     bool        tgdeferrable;
     bool        tginitdeferred;
     int16       tgnargs;
index b1210841fff99b3dc6971dfd770191920f26bc55..a2b64c78b3fed51fb5fb5464cb7fcd9e847173ce 100644 (file)
@@ -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))
index 2041125d4d085d6f5cda95352aef84eb1c3f6d25..40591fd368097099e70a324d656eac859851a985 100644 (file)
@@ -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))
index 1d0639bcf0c90fc9cf65e6f803fed3a2ccf3a79b..d29a6df21d3aab050614c43a3d60bfc767a4115c 100644 (file)
@@ -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,
                          ' ',
                          ' ',
index 52554c3b21d05d44600d8264647f7020eeeb5686..ae8965e730a4e2bac7c4c138f318dd969c864993 100644 (file)
@@ -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,
                                           ' ',
                                           ' ',
index 7c2cd4b8a4970902c07aecb5696298d5b290da7d..2213192f78299b2b558f5025b1ddba20ba49c62c 100644 (file)
@@ -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))
index 53249facb7dddc3824c32858a230c28249bab888..ede6607b851daed50cc1160f70f1c38af5cc0474 100644 (file)
@@ -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,97 +564,10 @@ 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,
index c11f337eed8d139478c2efd33d5bb98b3ffde5cd..63779452850f7a37f732e6c190f2b8745adff737 100644 (file)
@@ -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))
index 349eadf24f040b948a9e445a01f58965424e9cf8..37cc0e6f571835b693c2800a48aab5fc70dfb37b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.30 2007/01/05 22:19:25 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.31 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -146,7 +146,7 @@ RenameConversion(List *name, const char *newname)
                 errmsg("conversion \"%s\" does not exist",
                        NameListToString(name))));
 
-   tup = SearchSysCacheCopy(CONOID,
+   tup = SearchSysCacheCopy(CONVOID,
                             ObjectIdGetDatum(conversionOid),
                             0, 0, 0);
    if (!HeapTupleIsValid(tup)) /* should not happen */
@@ -236,7 +236,7 @@ AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId)
 
    Assert(RelationGetRelid(rel) == ConversionRelationId);
 
-   tup = SearchSysCacheCopy(CONOID,
+   tup = SearchSysCacheCopy(CONVOID,
                             ObjectIdGetDatum(conversionOid),
                             0, 0, 0);
    if (!HeapTupleIsValid(tup)) /* should not happen */
index ae9839641e572727d90249437c3b9e224d2caa24..9ea32d7707aeb49877634745ed974956a6a69466 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.153 2007/01/05 22:19:26 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.154 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -327,8 +327,8 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
                if (instr->ntuples == 0)
                    continue;
 
-               if (trig->tgisconstraint &&
-               (conname = GetConstraintNameForTrigger(trig->tgoid)) != NULL)
+               if (OidIsValid(trig->tgconstraint) &&
+                   (conname = get_constraint_name(trig->tgconstraint)) != NULL)
                {
                    appendStringInfo(&buf, "Trigger for constraint %s",
                                     conname);
index 001bc5715484b6a5da663da859ddbcd81dec49ae..cea8ddeabf98820d2bf1dc01fefa1b86720ad2be 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.213 2007/02/02 00:07:02 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.214 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,7 @@
 #include "catalog/pg_depend.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/toasting.h"
@@ -139,6 +140,7 @@ typedef struct NewConstraint
    char       *name;           /* Constraint name, or NULL if none */
    ConstrType  contype;        /* CHECK or FOREIGN */
    Oid         refrelid;       /* PK rel, if FOREIGN */
+   Oid         conid;          /* OID of pg_constraint entry, if FOREIGN */
    Node       *qual;           /* Check expr or FkConstraint struct */
    List       *qualstate;      /* Execution state for CHECK */
 } NewConstraint;
@@ -186,10 +188,9 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
                        int numattrs, int16 *attnums,
                        Oid *opclasses);
 static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
-                            Relation rel, Relation pkrel);
+                            Relation rel, Relation pkrel, Oid constraintOid);
 static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
-                        Oid constrOid);
-static char *fkMatchTypeToString(char match_type);
+                        Oid constraintOid);
 static void ATController(Relation rel, List *cmds, bool recurse);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
          bool recurse, bool recursing);
@@ -256,11 +257,6 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecAddInherit(Relation rel, RangeVar *parent);
 static void ATExecDropInherit(Relation rel, RangeVar *parent);
 static void copy_relation_data(Relation rel, SMgrRelation dst);
-static void update_ri_trigger_args(Oid relid,
-                      const char *oldname,
-                      const char *newname,
-                      bool fk_scan,
-                      bool update_relname);
 
 
 /* ----------------------------------------------------------------
@@ -1615,21 +1611,6 @@ renameatt(Oid myrelid,
 
    heap_close(attrelation, RowExclusiveLock);
 
-   /*
-    * Update att name in any RI triggers associated with the relation.
-    */
-   if (targetrelation->rd_rel->reltriggers > 0)
-   {
-       /* update tgargs column reference where att is primary key */
-       update_ri_trigger_args(RelationGetRelid(targetrelation),
-                              oldattname, newattname,
-                              false, false);
-       /* update tgargs column reference where att is foreign key */
-       update_ri_trigger_args(RelationGetRelid(targetrelation),
-                              oldattname, newattname,
-                              true, false);
-   }
-
    relation_close(targetrelation, NoLock);     /* close rel but keep lock */
 }
 
@@ -1708,226 +1689,12 @@ renamerel(Oid myrelid, const char *newrelname)
    if (relkind != RELKIND_INDEX)
        TypeRename(oldrelname, namespaceId, newrelname);
 
-   /*
-    * Update rel name in any RI triggers associated with the relation.
-    */
-   if (relhastriggers)
-   {
-       /* update tgargs where relname is primary key */
-       update_ri_trigger_args(myrelid,
-                              oldrelname,
-                              newrelname,
-                              false, true);
-       /* update tgargs where relname is foreign key */
-       update_ri_trigger_args(myrelid,
-                              oldrelname,
-                              newrelname,
-                              true, true);
-   }
-
    /*
     * Close rel, but keep exclusive lock!
     */
    relation_close(targetrelation, NoLock);
 }
 
-/*
- * Scan pg_trigger for RI triggers that are on the specified relation
- * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
- * is true).  Update RI trigger args fields matching oldname to contain
- * newname instead.  If update_relname is true, examine the relname
- * fields; otherwise examine the attname fields.
- */
-static void
-update_ri_trigger_args(Oid relid,
-                      const char *oldname,
-                      const char *newname,
-                      bool fk_scan,
-                      bool update_relname)
-{
-   Relation    tgrel;
-   ScanKeyData skey[1];
-   SysScanDesc trigscan;
-   HeapTuple   tuple;
-   Datum       values[Natts_pg_trigger];
-   char        nulls[Natts_pg_trigger];
-   char        replaces[Natts_pg_trigger];
-
-   tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
-   if (fk_scan)
-   {
-       ScanKeyInit(&skey[0],
-                   Anum_pg_trigger_tgconstrrelid,
-                   BTEqualStrategyNumber, F_OIDEQ,
-                   ObjectIdGetDatum(relid));
-       trigscan = systable_beginscan(tgrel, TriggerConstrRelidIndexId,
-                                     true, SnapshotNow,
-                                     1, skey);
-   }
-   else
-   {
-       ScanKeyInit(&skey[0],
-                   Anum_pg_trigger_tgrelid,
-                   BTEqualStrategyNumber, F_OIDEQ,
-                   ObjectIdGetDatum(relid));
-       trigscan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
-                                     true, SnapshotNow,
-                                     1, skey);
-   }
-
-   while ((tuple = systable_getnext(trigscan)) != NULL)
-   {
-       Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-       bytea      *val;
-       bytea      *newtgargs;
-       bool        isnull;
-       int         tg_type;
-       bool        examine_pk;
-       bool        changed;
-       int         tgnargs;
-       int         i;
-       int         newlen;
-       const char *arga[RI_MAX_ARGUMENTS];
-       const char *argp;
-
-       tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid);
-       if (tg_type == RI_TRIGGER_NONE)
-       {
-           /* Not an RI trigger, forget it */
-           continue;
-       }
-
-       /*
-        * It is an RI trigger, so parse the tgargs bytea.
-        *
-        * NB: we assume the field will never be compressed or moved out of
-        * line; so does trigger.c ...
-        */
-       tgnargs = pg_trigger->tgnargs;
-       val = DatumGetByteaP(fastgetattr(tuple,
-                                       Anum_pg_trigger_tgargs,
-                                       tgrel->rd_att, &isnull));
-       if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
-           tgnargs > RI_MAX_ARGUMENTS)
-       {
-           /* This probably shouldn't happen, but ignore busted triggers */
-           continue;
-       }
-       argp = (const char *) VARDATA(val);
-       for (i = 0; i < tgnargs; i++)
-       {
-           arga[i] = argp;
-           argp += strlen(argp) + 1;
-       }
-
-       /*
-        * Figure out which item(s) to look at.  If the trigger is primary-key
-        * type and attached to my rel, I should look at the PK fields; if it
-        * is foreign-key type and attached to my rel, I should look at the FK
-        * fields.  But the opposite rule holds when examining triggers found
-        * by tgconstrrel search.
-        */
-       examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
-
-       changed = false;
-       if (update_relname)
-       {
-           /* Change the relname if needed */
-           i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
-           if (strcmp(arga[i], oldname) == 0)
-           {
-               arga[i] = newname;
-               changed = true;
-           }
-       }
-       else
-       {
-           /* Change attname(s) if needed */
-           i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
-               RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
-           for (; i < tgnargs; i += 2)
-           {
-               if (strcmp(arga[i], oldname) == 0)
-               {
-                   arga[i] = newname;
-                   changed = true;
-               }
-           }
-       }
-
-       if (!changed)
-       {
-           /* Don't need to update this tuple */
-           continue;
-       }
-
-       /*
-        * Construct modified tgargs bytea.
-        */
-       newlen = VARHDRSZ;
-       for (i = 0; i < tgnargs; i++)
-           newlen += strlen(arga[i]) + 1;
-       newtgargs = (bytea *) palloc(newlen);
-       VARATT_SIZEP(newtgargs) = newlen;
-       newlen = VARHDRSZ;
-       for (i = 0; i < tgnargs; i++)
-       {
-           strcpy(((char *) newtgargs) + newlen, arga[i]);
-           newlen += strlen(arga[i]) + 1;
-       }
-
-       /*
-        * Build modified tuple.
-        */
-       for (i = 0; i < Natts_pg_trigger; i++)
-       {
-           values[i] = (Datum) 0;
-           replaces[i] = ' ';
-           nulls[i] = ' ';
-       }
-       values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
-       replaces[Anum_pg_trigger_tgargs - 1] = 'r';
-
-       tuple = heap_modifytuple(tuple, RelationGetDescr(tgrel), values, nulls, replaces);
-
-       /*
-        * Update pg_trigger and its indexes
-        */
-       simple_heap_update(tgrel, &tuple->t_self, tuple);
-
-       CatalogUpdateIndexes(tgrel, tuple);
-
-       /*
-        * Invalidate trigger's relation's relcache entry so that other
-        * backends (and this one too!) are sent SI message to make them
-        * rebuild relcache entries.  (Ideally this should happen
-        * automatically...)
-        *
-        * We can skip this for triggers on relid itself, since that relcache
-        * flush will happen anyway due to the table or column rename.  We
-        * just need to catch the far ends of RI relationships.
-        */
-       pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-       if (pg_trigger->tgrelid != relid)
-           CacheInvalidateRelcacheByRelid(pg_trigger->tgrelid);
-
-       /* free up our scratch memory */
-       pfree(newtgargs);
-       heap_freetuple(tuple);
-   }
-
-   systable_endscan(trigscan);
-
-   heap_close(tgrel, RowExclusiveLock);
-
-   /*
-    * Increment cmd counter to make updates visible; this is needed in case
-    * the same tuple has to be updated again by next pass (can happen in case
-    * of a self-referential FK relationship).
-    */
-   CommandCounterIncrement();
-}
-
 /*
  * AlterTable
  *     Execute ALTER TABLE, which can be a list of subcommands
@@ -2552,7 +2319,8 @@ ATRewriteTables(List **wqueue)
 
                refrel = heap_open(con->refrelid, RowShareLock);
 
-               validateForeignKeyConstraint(fkconstraint, rel, refrel);
+               validateForeignKeyConstraint(fkconstraint, rel, refrel,
+                                            con->conid);
 
                heap_close(refrel, NoLock);
            }
@@ -4061,6 +3829,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
    Oid         pktypoid[INDEX_MAX_KEYS];
    Oid         fktypoid[INDEX_MAX_KEYS];
    Oid         opclasses[INDEX_MAX_KEYS];
+   Oid         pfeqoperators[INDEX_MAX_KEYS];
+   Oid         ppeqoperators[INDEX_MAX_KEYS];
+   Oid         ffeqoperators[INDEX_MAX_KEYS];
    int         i;
    int         numfks,
                numpks;
@@ -4138,6 +3909,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
    MemSet(pktypoid, 0, sizeof(pktypoid));
    MemSet(fktypoid, 0, sizeof(fktypoid));
    MemSet(opclasses, 0, sizeof(opclasses));
+   MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
+   MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
+   MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
 
    numfks = transformColumnNameList(RelationGetRelid(rel),
                                     fkconstraint->fk_attrs,
@@ -4166,7 +3940,15 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
                                           opclasses);
    }
 
-   /* Be sure referencing and referenced column types are comparable */
+   /*
+    * Look up the equality operators to use in the constraint.
+    *
+    * Note that we look for operator(s) with the PK type on the left; when
+    * the types are different this is the right choice because the PK index
+    * will need operators with the indexkey on the left.  Also, we take the
+    * PK type as being the declared input type of the opclass, which might be
+    * binary-compatible but not identical to the PK column type.
+    */
    if (numfks != numpks)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_FOREIGN_KEY),
@@ -4174,24 +3956,71 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 
    for (i = 0; i < numpks; i++)
    {
+       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,
@@ -4268,6 +4068,24 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
     */
    createForeignKeyTriggers(rel, fkconstraint, constrOid);
 
+   /*
+    * Tell Phase 3 to check that the constraint is satisfied by existing rows
+    * (we can skip this during table creation).
+    */
+   if (!fkconstraint->skip_validation)
+   {
+       NewConstraint *newcon;
+
+       newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+       newcon->name = fkconstraint->constr_name;
+       newcon->contype = CONSTR_FOREIGN;
+       newcon->refrelid = RelationGetRelid(pkrel);
+       newcon->conid = constrOid;
+       newcon->qual = (Node *) fkconstraint;
+
+       tab->constraints = lappend(tab->constraints, newcon);
+   }
+
    /*
     * Close pk table, but keep lock until we've committed.
     */
@@ -4526,25 +4344,15 @@ transformFkeyCheckAttrs(Relation pkrel,
 static void
 validateForeignKeyConstraint(FkConstraint *fkconstraint,
                             Relation rel,
-                            Relation pkrel)
+                            Relation pkrel,
+                            Oid constraintOid)
 {
    HeapScanDesc scan;
    HeapTuple   tuple;
    Trigger     trig;
-   ListCell   *list;
-   int         count;
 
    /*
-    * See if we can do it with a single LEFT JOIN query.  A FALSE result
-    * indicates we must proceed with the fire-the-trigger method.
-    */
-   if (RI_Initial_Check(fkconstraint, rel, pkrel))
-       return;
-
-   /*
-    * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
-    * if that tuple had just been inserted.  If any of those fail, it should
-    * ereport(ERROR) and that's that.
+    * Build a trigger call structure; we'll need it either way.
     */
    MemSet(&trig, 0, sizeof(trig));
    trig.tgoid = InvalidOid;
@@ -4552,35 +4360,23 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
    trig.tgenabled = TRUE;
    trig.tgisconstraint = TRUE;
    trig.tgconstrrelid = RelationGetRelid(pkrel);
+   trig.tgconstraint = constraintOid;
    trig.tgdeferrable = FALSE;
    trig.tginitdeferred = FALSE;
+   /* we needn't fill in tgargs */
 
-   trig.tgargs = (char **) palloc(sizeof(char *) *
-                                  (4 + list_length(fkconstraint->fk_attrs)
-                                   + list_length(fkconstraint->pk_attrs)));
-
-   trig.tgargs[0] = trig.tgname;
-   trig.tgargs[1] = RelationGetRelationName(rel);
-   trig.tgargs[2] = RelationGetRelationName(pkrel);
-   trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
-   count = 4;
-   foreach(list, fkconstraint->fk_attrs)
-   {
-       char       *fk_at = strVal(lfirst(list));
-
-       trig.tgargs[count] = fk_at;
-       count += 2;
-   }
-   count = 5;
-   foreach(list, fkconstraint->pk_attrs)
-   {
-       char       *pk_at = strVal(lfirst(list));
-
-       trig.tgargs[count] = pk_at;
-       count += 2;
-   }
-   trig.tgnargs = count - 1;
+   /*
+    * See if we can do it with a single LEFT JOIN query.  A FALSE result
+    * indicates we must proceed with the fire-the-trigger method.
+    */
+   if (RI_Initial_Check(&trig, rel, pkrel))
+       return;
 
+   /*
+    * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
+    * if that tuple had just been inserted.  If any of those fail, it should
+    * ereport(ERROR) and that's that.
+    */
    scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
 
    while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
@@ -4613,18 +4409,13 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
    }
 
    heap_endscan(scan);
-
-   pfree(trig.tgargs);
 }
 
 static void
 CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
-                    ObjectAddress *constrobj, ObjectAddress *trigobj,
-                    bool on_insert)
+                    Oid constraintOid, bool on_insert)
 {
    CreateTrigStmt *fk_trigger;
-   ListCell   *fk_attr;
-   ListCell   *pk_attr;
 
    fk_trigger = makeNode(CreateTrigStmt);
    fk_trigger->trigname = fkconstraint->constr_name;
@@ -4649,32 +4440,9 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
    fk_trigger->deferrable = fkconstraint->deferrable;
    fk_trigger->initdeferred = fkconstraint->initdeferred;
    fk_trigger->constrrel = fkconstraint->pktable;
-
    fk_trigger->args = NIL;
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(fkconstraint->constr_name));
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(myRel->relname));
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(fkconstraint->pktable->relname));
-   fk_trigger->args = lappend(fk_trigger->args,
-               makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-   if (list_length(fkconstraint->fk_attrs) != list_length(fkconstraint->pk_attrs))
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_FOREIGN_KEY),
-                errmsg("number of referencing and referenced columns for foreign key disagree")));
-
-   forboth(fk_attr, fkconstraint->fk_attrs,
-           pk_attr, fkconstraint->pk_attrs)
-   {
-       fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-       fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-   }
 
-   trigobj->objectId = CreateTrigger(fk_trigger, true);
-
-   /* Register dependency from trigger to constraint */
-   recordDependencyOn(trigobj, constrobj, DEPENDENCY_INTERNAL);
+   (void) CreateTrigger(fk_trigger, constraintOid);
 
    /* Make changes-so-far visible */
    CommandCounterIncrement();
@@ -4685,14 +4453,10 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
  */
 static void
 createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
-                        Oid constrOid)
+                        Oid constraintOid)
 {
    RangeVar   *myRel;
    CreateTrigStmt *fk_trigger;
-   ListCell   *fk_attr;
-   ListCell   *pk_attr;
-   ObjectAddress trigobj,
-               constrobj;
 
    /*
     * Reconstruct a RangeVar for my relation (not passed in, unfortunately).
@@ -4700,15 +4464,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
    myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
                         pstrdup(RelationGetRelationName(rel)));
 
-   /*
-    * Preset objectAddress fields
-    */
-   constrobj.classId = ConstraintRelationId;
-   constrobj.objectId = constrOid;
-   constrobj.objectSubId = 0;
-   trigobj.classId = TriggerRelationId;
-   trigobj.objectSubId = 0;
-
    /* Make changes-so-far visible */
    CommandCounterIncrement();
 
@@ -4716,8 +4471,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
     * Build and execute a CREATE CONSTRAINT TRIGGER statement for the CHECK
     * action for both INSERTs and UPDATEs on the referencing table.
     */
-   CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, true);
-   CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, false);
+   CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, true);
+   CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, false);
 
    /*
     * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
@@ -4765,27 +4520,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
                 (int) fkconstraint->fk_del_action);
            break;
    }
-
    fk_trigger->args = NIL;
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(fkconstraint->constr_name));
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(myRel->relname));
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(fkconstraint->pktable->relname));
-   fk_trigger->args = lappend(fk_trigger->args,
-               makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-   forboth(fk_attr, fkconstraint->fk_attrs,
-           pk_attr, fkconstraint->pk_attrs)
-   {
-       fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-       fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-   }
-
-   trigobj.objectId = CreateTrigger(fk_trigger, true);
 
-   /* Register dependency from trigger to constraint */
-   recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+   (void) CreateTrigger(fk_trigger, constraintOid);
 
    /* Make changes-so-far visible */
    CommandCounterIncrement();
@@ -4835,49 +4572,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
                 (int) fkconstraint->fk_upd_action);
            break;
    }
-
    fk_trigger->args = NIL;
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(fkconstraint->constr_name));
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(myRel->relname));
-   fk_trigger->args = lappend(fk_trigger->args,
-                              makeString(fkconstraint->pktable->relname));
-   fk_trigger->args = lappend(fk_trigger->args,
-               makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-   forboth(fk_attr, fkconstraint->fk_attrs,
-           pk_attr, fkconstraint->pk_attrs)
-   {
-       fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-       fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-   }
-
-   trigobj.objectId = CreateTrigger(fk_trigger, true);
 
-   /* Register dependency from trigger to constraint */
-   recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
-}
-
-/*
- * fkMatchTypeToString -
- *   convert FKCONSTR_MATCH_xxx code to string to use in trigger args
- */
-static char *
-fkMatchTypeToString(char match_type)
-{
-   switch (match_type)
-   {
-       case FKCONSTR_MATCH_FULL:
-           return pstrdup("FULL");
-       case FKCONSTR_MATCH_PARTIAL:
-           return pstrdup("PARTIAL");
-       case FKCONSTR_MATCH_UNSPECIFIED:
-           return pstrdup("UNSPECIFIED");
-       default:
-           elog(ERROR, "unrecognized match type: %d",
-                (int) match_type);
-   }
-   return NULL;                /* can't get here */
+   (void) CreateTrigger(fk_trigger, constraintOid);
 }
 
 /*
index cc7dfc895b83d5ddf7e20610724ea4416b91dd68..c08525c2e040ffb41b44ce4fa9a060b4713c2cbe 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.212 2007/01/25 04:17:46 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.213 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -57,13 +58,12 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
 /*
  * Create a trigger.  Returns the OID of the created trigger.
  *
- * forConstraint, if true, says that this trigger is being created to
- * implement a constraint. The caller will then be expected to make
- * a pg_depend entry linking the trigger to that constraint (and thereby
- * to the owning relation(s)).
+ * constraintOid, if nonzero, says that this trigger is being created to
+ * implement that constraint.  A suitable pg_depend entry will be made
+ * to link the trigger to that constraint.
  */
 Oid
-CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
+CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
 {
    int16       tgtype;
    int2vector *tgattr;
@@ -91,51 +91,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
    rel = heap_openrv(stmt->relation, AccessExclusiveLock);
 
-   if (stmt->constrrel != NULL)
-       constrrelid = RangeVarGetRelid(stmt->constrrel, false);
-   else if (stmt->isconstraint)
-   {
-       /*
-        * If this trigger is a constraint (and a foreign key one) then we
-        * really need a constrrelid.  Since we don't have one, we'll try to
-        * generate one from the argument information.
-        *
-        * This is really just a workaround for a long-ago pg_dump bug that
-        * omitted the FROM clause in dumped CREATE CONSTRAINT TRIGGER
-        * commands.  We don't want to bomb out completely here if we can't
-        * determine the correct relation, because that would prevent loading
-        * the dump file.  Instead, NOTICE here and ERROR in the trigger.
-        */
-       bool        needconstrrelid = false;
-       void       *elem = NULL;
-
-       if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check_", 14) == 0)
-       {
-           /* A trigger on FK table. */
-           needconstrrelid = true;
-           if (list_length(stmt->args) > RI_PK_RELNAME_ARGNO)
-               elem = list_nth(stmt->args, RI_PK_RELNAME_ARGNO);
-       }
-       else if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_", 8) == 0)
-       {
-           /* A trigger on PK table. */
-           needconstrrelid = true;
-           if (list_length(stmt->args) > RI_FK_RELNAME_ARGNO)
-               elem = list_nth(stmt->args, RI_FK_RELNAME_ARGNO);
-       }
-       if (elem != NULL)
-       {
-           RangeVar   *rel = makeRangeVar(NULL, strVal(elem));
-
-           constrrelid = RangeVarGetRelid(rel, true);
-       }
-       if (needconstrrelid && constrrelid == InvalidOid)
-           ereport(NOTICE,
-                   (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                    errmsg("could not determine referenced table for constraint \"%s\"",
-                           stmt->trigname)));
-   }
-
    if (rel->rd_rel->relkind != RELKIND_RELATION)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -152,15 +107,17 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
    if (stmt->isconstraint)
    {
-       /* foreign key constraint trigger */
-
+       /* constraint trigger */
        aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
                                      ACL_REFERENCES);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error(aclresult, ACL_KIND_CLASS,
                           RelationGetRelationName(rel));
-       if (constrrelid != InvalidOid)
+
+       if (stmt->constrrel != NULL)
        {
+           constrrelid = RangeVarGetRelid(stmt->constrrel, false);
+
            aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
                                          ACL_REFERENCES);
            if (aclresult != ACLCHECK_OK)
@@ -170,7 +127,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
    }
    else
    {
-       /* real trigger */
+       /* regular trigger */
        aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
                                      ACL_TRIGGER);
        if (aclresult != ACLCHECK_OK)
@@ -187,23 +144,31 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
    trigoid = GetNewOid(tgrel);
 
    /*
-    * If trigger is an RI constraint, use specified trigger name as
-    * constraint name and build a unique trigger name instead. This is mainly
-    * for backwards compatibility with CREATE CONSTRAINT TRIGGER commands.
+    * If trigger is for an RI constraint, the passed-in name is the
+    * constraint name; save that and build a unique trigger name to avoid
+    * collisions with user-selected trigger names.
     */
-   if (stmt->isconstraint)
+   if (OidIsValid(constraintOid))
    {
        snprintf(constrtrigname, sizeof(constrtrigname),
                 "RI_ConstraintTrigger_%u", trigoid);
        trigname = constrtrigname;
        constrname = stmt->trigname;
    }
+   else if (stmt->isconstraint)
+   {
+       /* constraint trigger: trigger name is also constraint name */
+       trigname = stmt->trigname;
+       constrname = stmt->trigname;
+   }
    else
    {
+       /* regular trigger: use empty constraint name */
        trigname = stmt->trigname;
        constrname = "";
    }
 
+   /* Compute tgtype */
    TRIGGER_CLEAR_TYPE(tgtype);
    if (stmt->before)
        TRIGGER_SETT_BEFORE(tgtype);
@@ -298,7 +263,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
    /*
     * Build the new pg_trigger tuple.
     */
-   MemSet(nulls, ' ', Natts_pg_trigger * sizeof(char));
+   memset(nulls, ' ', Natts_pg_trigger * sizeof(char));
 
    values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
    values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
@@ -310,6 +275,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
    values[Anum_pg_trigger_tgconstrname - 1] = DirectFunctionCall1(namein,
                                                CStringGetDatum(constrname));
    values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
+   values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
    values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
    values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
 
@@ -372,10 +338,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
    CatalogUpdateIndexes(tgrel, tuple);
 
-   myself.classId = TriggerRelationId;
-   myself.objectId = trigoid;
-   myself.objectSubId = 0;
-
    heap_freetuple(tuple);
    heap_close(tgrel, RowExclusiveLock);
 
@@ -412,20 +374,36 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
    /*
     * Record dependencies for trigger.  Always place a normal dependency on
-    * the function.  If we are doing this in response to an explicit CREATE
-    * TRIGGER command, also make trigger be auto-dropped if its relation is
-    * dropped or if the FK relation is dropped.  (Auto drop is compatible
-    * with our pre-7.3 behavior.)  If the trigger is being made for a
-    * constraint, we can skip the relation links; the dependency on the
-    * constraint will indirectly depend on the relations.
+    * the function.
     */
+   myself.classId = TriggerRelationId;
+   myself.objectId = trigoid;
+   myself.objectSubId = 0;
+
    referenced.classId = ProcedureRelationId;
    referenced.objectId = funcoid;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
-   if (!forConstraint)
+   if (OidIsValid(constraintOid))
    {
+       /*
+        * It's for a constraint, so make it an internal dependency of the
+        * constraint.  We can skip depending on the relations, as there'll
+        * be an indirect dependency via the constraint.
+        */
+       referenced.classId = ConstraintRelationId;
+       referenced.objectId = constraintOid;
+       referenced.objectSubId = 0;
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+   }
+   else
+   {
+       /*
+        * Regular CREATE TRIGGER, so place dependencies.  We make trigger be
+        * auto-dropped if its relation is dropped or if the FK relation is
+        * dropped.  (Auto drop is compatible with our pre-7.3 behavior.)
+        */
        referenced.classId = RelationRelationId;
        referenced.objectId = RelationGetRelid(rel);
        referenced.objectSubId = 0;
@@ -773,7 +751,7 @@ EnableDisableTrigger(Relation rel, const char *tgname,
    {
        Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);
 
-       if (oldtrig->tgisconstraint)
+       if (OidIsValid(oldtrig->tgconstraint))
        {
            /* system trigger ... ok to process? */
            if (skip_system)
@@ -886,6 +864,7 @@ RelationBuildTriggers(Relation relation)
        build->tgenabled = pg_trigger->tgenabled;
        build->tgisconstraint = pg_trigger->tgisconstraint;
        build->tgconstrrelid = pg_trigger->tgconstrrelid;
+       build->tgconstraint = pg_trigger->tgconstraint;
        build->tgdeferrable = pg_trigger->tgdeferrable;
        build->tginitdeferred = pg_trigger->tginitdeferred;
        build->tgnargs = pg_trigger->tgnargs;
@@ -1220,6 +1199,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
                return false;
            if (trig1->tgconstrrelid != trig2->tgconstrrelid)
                return false;
+           if (trig1->tgconstraint != trig2->tgconstraint)
+               return false;
            if (trig1->tgdeferrable != trig2->tgdeferrable)
                return false;
            if (trig1->tginitdeferred != trig2->tginitdeferred)
index 4f88e9561cd1a8eaf931fdc90da939f4c19af745..99f964db74df0ac9fc8a7988171f2035436151b1 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.99 2007/01/05 22:19:26 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.100 2007/02/14 01:58:57 tgl Exp $
  *
  * DESCRIPTION
  *   The "DefineFoo" routines take the parse tree and pick out the
@@ -1966,6 +1966,9 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
                          domainOid,    /* domain constraint */
                          InvalidOid,   /* Foreign key fields */
                          NULL,
+                         NULL,
+                         NULL,
+                         NULL,
                          0,
                          ' ',
                          ' ',
index 613ef653d19323d4ea078647915704d5ff5f67ee..5cabec2970b27cb48ff43d500707f24e22109852 100644 (file)
@@ -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:
index c21d827d0c7070233c1357430febb1aeda456efb..4ba2bb7a98f05f1dc36f96e92bcf00e4c8c646d3 100644 (file)
@@ -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 $
  *
  * ----------
  */
 
 #include "postgres.h"
 
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_operator.h"
 #include "commands/trigger.h"
 #include "executor/spi_priv.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
+#include "miscadmin.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
-#include "utils/typcache.h"
-#include "miscadmin.h"
+#include "utils/memutils.h"
 
 
 /* ----------
  * ----------
  */
 
-#define RI_INIT_QUERYHASHSIZE          128
+#define RI_MAX_NUMKEYS                 INDEX_MAX_KEYS
 
-#define RI_MATCH_TYPE_UNSPECIFIED      0
-#define RI_MATCH_TYPE_FULL             1
-#define RI_MATCH_TYPE_PARTIAL          2
+#define RI_INIT_QUERYHASHSIZE          128
 
 #define RI_KEYS_ALL_NULL               0
 #define RI_KEYS_SOME_NULL              1
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
 
+#define RIAttName(rel, attnum)  NameStr(*attnumAttName(rel, attnum))
+#define RIAttType(rel, attnum)  SPI_gettypeid(RelationGetDescr(rel), attnum)
+
 #define RI_TRIGTYPE_INSERT 1
 #define RI_TRIGTYPE_UPDATE 2
 #define RI_TRIGTYPE_INUP   3
 #define RI_TRIGTYPE_DELETE 4
 
+#define RI_KEYPAIR_FK_IDX  0
+#define RI_KEYPAIR_PK_IDX  1
+
+
+/* ----------
+ * RI_ConstraintInfo
+ *
+ * Information extracted from an FK pg_constraint entry.
+ * ----------
+ */
+typedef struct RI_ConstraintInfo
+{
+   Oid         constraint_id;  /* OID of pg_constraint entry */
+   NameData    conname;        /* name of the FK constraint */
+   Oid         pk_relid;       /* referenced relation */
+   Oid         fk_relid;       /* referencing relation */
+   char        confupdtype;    /* foreign key's ON UPDATE action */
+   char        confdeltype;    /* foreign key's ON DELETE action */
+   char        confmatchtype;  /* foreign key's match type */
+   int         nkeys;          /* number of key columns */
+   int16       pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
+   int16       fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
+   Oid         pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
+   Oid         pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
+   Oid         ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
+} RI_ConstraintInfo;
+
 
 /* ----------
  * RI_QueryKey
  *
- * The key identifying a prepared SPI plan in our private hashtable
+ * The key identifying a prepared SPI plan in our query hashtable
  * ----------
  */
 typedef struct RI_QueryKey
 {
-   int32       constr_type;
+   char        constr_type;
    Oid         constr_id;
    int32       constr_queryno;
    Oid         fk_relid;
@@ -107,11 +139,38 @@ typedef struct RI_QueryHashEntry
 } 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 <referential constraint definition>
@@ -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 <unspecified> - 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 <pktable> WHERE pkatt1 = $1 [AND ...]
+        *  SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
         * The type id's for the $ parameters are those of the
-        * corresponding FK attributes. Thus, ri_PlanCheck could
-        * eventually fail if the parser cannot identify some way
-        * how to compare these two types by '='.
+        * corresponding FK attributes.
         * ----------
         */
+       initStringInfo(&querybuf);
        quoteRelationName(pkrelname, pk_rel);
-       snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname);
+       appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
        querysep = "WHERE";
-       for (i = 0; i < qkey.nkeypairs; i++)
+       for (i = 0; i < riinfo.nkeys; i++)
        {
+           Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+           Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
            quoteOneName(attname,
-                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
-           snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                    querysep, attname, i + 1);
+                        RIAttName(pk_rel, riinfo.pk_attnums[i]));
+           sprintf(paramname, "$%d", i + 1);
+           ri_GenerateQual(&querybuf, querysep,
+                           attname, pk_type,
+                           riinfo.pf_eq_oprs[i],
+                           paramname, fk_type);
            querysep = "AND";
-           queryoids[i] = SPI_gettypeid(fk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_FK_IDX]);
+           queryoids[i] = fk_type;
        }
-       strcat(querystr, " FOR SHARE OF x");
+       appendStringInfo(&querybuf, " FOR SHARE OF x");
 
        /* Prepare and save the plan */
-       qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+       qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                             &qkey, fk_rel, pk_rel, true);
    }
 
@@ -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 <unspecified>/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 <pktable> WHERE pkatt1 = $1 [AND ...]
+        *  SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
         * The type id's for the $ parameters are those of the
-        * corresponding FK attributes. Thus, ri_PlanCheck could
-        * eventually fail if the parser cannot identify some way
-        * how to compare these two types by '='.
+        * PK attributes themselves.
         * ----------
         */
+       initStringInfo(&querybuf);
        quoteRelationName(pkrelname, pk_rel);
-       snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname);
+       appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
        querysep = "WHERE";
-       for (i = 0; i < qkey.nkeypairs; i++)
+       for (i = 0; i < riinfo->nkeys; i++)
        {
+           Oid     pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+
            quoteOneName(attname,
-                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
-           snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                    querysep, attname, i + 1);
+                        RIAttName(pk_rel, riinfo->pk_attnums[i]));
+           sprintf(paramname, "$%d", i + 1);
+           ri_GenerateQual(&querybuf, querysep,
+                           attname, pk_type,
+                           riinfo->pp_eq_oprs[i],
+                           paramname, pk_type);
            querysep = "AND";
-           queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+           queryoids[i] = pk_type;
        }
-       strcat(querystr, " FOR SHARE OF x");
+       appendStringInfo(&querybuf, " FOR SHARE OF x");
 
        /* Prepare and save the plan */
-       qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+       qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
                             &qkey, fk_rel, pk_rel, true);
    }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> WHERE fkatt1 = $1 [AND ...]
+                *  SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+                                fkrelname);
                querysep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                            querysep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&querybuf, querysep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, " FOR SHARE OF x");
+               appendStringInfo(&querybuf, " FOR SHARE OF x");
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> WHERE fkatt1 = $1 [AND ...]
+                *  SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+                                fkrelname);
                querysep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                            querysep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&querybuf, querysep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, " FOR SHARE OF x");
+               appendStringInfo(&querybuf, " FOR SHARE OF x");
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> WHERE fkatt1 = $1 [AND ...]
+                *  DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "DELETE FROM ONLY %s", fkrelname);
+               appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
                querysep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                            querysep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&querybuf, querysep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> SET fkatt1 = $1 [, ...]
-                *          WHERE fkatt1 = $n [AND ...]
+                *          WHERE $n = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.  Note that we are assuming
+                * there is an assignment cast from the PK to the FK type;
+                * else the parser will fail.
                 * ----------
                 */
+               initStringInfo(&querybuf);
+               initStringInfo(&qualbuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-               qualstr[0] = '\0';
+               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
                querysep = "";
                qualsep = "WHERE";
-               for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
+               for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = $%d",
-                            querysep, attname, i + 1);
-                   snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-                            qualsep, attname, j + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   appendStringInfo(&querybuf,
+                                    "%s %s = $%d",
+                                    querysep, attname, i + 1);
+                   sprintf(paramname, "$%d", j + 1);
+                   ri_GenerateQual(&qualbuf, qualsep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = ",";
                    qualsep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
-                   queryoids[j] = queryoids[i];
+                   queryoids[i] = pk_type;
+                   queryoids[j] = pk_type;
                }
-               strcat(querystr, qualstr);
+               appendStringInfoString(&querybuf, qualbuf.data);
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs * 2, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> WHERE fkatt1 = $1 [AND ...]
+                *  SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+                                fkrelname);
                querysep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                            querysep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&querybuf, querysep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, " FOR SHARE OF x");
+               appendStringInfo(&querybuf, " FOR SHARE OF x");
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> WHERE fkatt1 = $1 [AND ...]
+                *  SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+                                fkrelname);
                querysep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-                            querysep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&querybuf, querysep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, " FOR SHARE OF x");
+               appendStringInfo(&querybuf, " FOR SHARE OF x");
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> SET fkatt1 = NULL [, ...]
-                *          WHERE fkatt1 = $1 [AND ...]
+                *          WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
+               initStringInfo(&qualbuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-               qualstr[0] = '\0';
+               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
                querysep = "";
                qualsep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
-                            querysep, attname);
-                   snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-                            qualsep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   appendStringInfo(&querybuf,
+                                    "%s %s = NULL",
+                                    querysep, attname);
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&qualbuf, qualsep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = ",";
                    qualsep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, qualstr);
+               appendStringInfoString(&querybuf, qualbuf.data);
 
                /* Prepare and save the plan */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, true);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> SET fkatt1 = NULL [, ...]
-                *          WHERE fkatt1 = $1 [AND ...]
+                *          WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
+               initStringInfo(&qualbuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-               qualstr[0] = '\0';
+               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
                querysep = "";
                qualsep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
-                   quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
 
+                   quoteOneName(attname,
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
                    /*
                     * MATCH <unspecified> - only change columns corresponding
                     * to changed columns in pk_rel's key
                     */
-                   if (match_type == RI_MATCH_TYPE_FULL ||
-                       !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
-                                       RI_KEYPAIR_PK_IDX))
+                   if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
+                       !ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+                                       &riinfo, true))
                    {
-                       snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
-                                querysep, attname);
+                       appendStringInfo(&querybuf,
+                                        "%s %s = NULL",
+                                        querysep, attname);
                        querysep = ",";
                    }
-                   snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-                            qualsep, attname, i + 1);
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&qualbuf, qualsep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    qualsep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, qualstr);
+               appendStringInfoString(&querybuf, qualbuf.data);
 
                /*
                 * Prepare the plan.  Save it only if we're building the
                 * "standard" plan.
                 */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel,
                                     use_cached_query);
            }
@@ -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 <referential constraint definition>
@@ -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 <fktable> SET fkatt1 = DEFAULT [, ...]
-                *          WHERE fkatt1 = $1 [AND ...]
+                *          WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
+               initStringInfo(&qualbuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-               qualstr[0] = '\0';
+               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
                querysep = "";
                qualsep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-                   snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT",
-                            querysep, attname);
-                   snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-                            qualsep, attname, i + 1);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
+                   appendStringInfo(&querybuf,
+                                    "%s %s = DEFAULT",
+                                    querysep, attname);
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&qualbuf, qualsep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    querysep = ",";
                    qualsep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, qualstr);
+               appendStringInfoString(&querybuf, qualbuf.data);
 
                /* Prepare the plan, don't save it */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, false);
            }
 
@@ -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 <referential constraint definition>
@@ -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 <fktable> SET fkatt1 = DEFAULT [, ...]
-                *          WHERE fkatt1 = $1 [AND ...]
+                *          WHERE $1 = fkatt1 [AND ...]
                 * The type id's for the $ parameters are those of the
-                * corresponding PK attributes. Thus, ri_PlanCheck could
-                * eventually fail if the parser cannot identify some way
-                * how to compare these two types by '='.
+                * corresponding PK attributes.
                 * ----------
                 */
+               initStringInfo(&querybuf);
+               initStringInfo(&qualbuf);
                quoteRelationName(fkrelname, fk_rel);
-               snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-               qualstr[0] = '\0';
+               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
                querysep = "";
                qualsep = "WHERE";
-               for (i = 0; i < qkey.nkeypairs; i++)
+               for (i = 0; i < riinfo.nkeys; i++)
                {
+                   Oid     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+                   Oid     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
                    quoteOneName(attname,
-                                tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
 
                    /*
                     * MATCH <unspecified> - only change columns corresponding
                     * to changed columns in pk_rel's key
                     */
-                   if (match_type == RI_MATCH_TYPE_FULL ||
-                       !ri_OneKeyEqual(pk_rel, i, old_row,
-                                       new_row, &qkey, RI_KEYPAIR_PK_IDX))
+                   if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
+                       !ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+                                       &riinfo, true))
                    {
-                       snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT",
-                                querysep, attname);
+                       appendStringInfo(&querybuf,
+                                        "%s %s = DEFAULT",
+                                        querysep, attname);
                        querysep = ",";
                    }
-                   snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-                            qualsep, attname, i + 1);
+                   sprintf(paramname, "$%d", i + 1);
+                   ri_GenerateQual(&qualbuf, qualsep,
+                                   paramname, pk_type,
+                                   riinfo.pf_eq_oprs[i],
+                                   attname, fk_type);
                    qualsep = "AND";
-                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-                                        qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+                   queryoids[i] = pk_type;
                }
-               strcat(querystr, qualstr);
+               appendStringInfoString(&querybuf, qualbuf.data);
 
                /* Prepare the plan, don't save it */
-               qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                     &qkey, fk_rel, pk_rel, false);
            }
 
@@ -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.
index 3cd317361f44801d5e0ff89ef28717a7a655201e..064b8e07f76b3c233b053b620bfcd23307b2fb97 100644 (file)
@@ -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;
 }
index 8946cb73151fcedab2f7741f0a7a9f9f825b5280..d6ff883c92497d7066869801064200de5a1b70dd 100644 (file)
@@ -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 ----------                      */
 
index 5b33ff423eaa7b3a423960da371083c29e2e78fc..75f290dda614f7a2e6e889de491734c8bf50fd77 100644 (file)
@@ -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,
index 2fec7326d50032faa1a1849f3d1f70864f4806e4..eef9a4c875a13a7861fc634124283a09348e0a30 100644 (file)
@@ -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,11 +3841,30 @@ 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
             */
+           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 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, "
index 6469b6aee4103ab32e794b23fadd39cb8c5efcae..3e830d9eb9adbf543545dcf9af2672a9684c0d93 100644 (file)
@@ -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)
index 5bdda05960ebd49a39cf933b076cd0d04b7b3dc9..aec8dee45ccf8d26f087d3e01e51256d84d33319 100644 (file)
@@ -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
index 73e2cf0cc7dd4c332d8da4587ac19ece8ac7c50d..baa650a8edb260b3f58ae9d7c217899717ce8945 100644 (file)
@@ -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));
index 6fbfb6a0cda61159c2471ff71b09a4f0459ccc47..d01c22c703ceabb8cadee1b5e831c144563f6cd2 100644 (file)
@@ -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
@@ -91,6 +91,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
     */
@@ -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);
 
index b5408253b34f9f665ae3db4858b4b1db3ffb58b2..df22089e3107677cae40a8dede04fbef63e3e11e 100644 (file)
@@ -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
 /* ----------------
  *     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)
index 284815b47f734627c59d5173ac601d8d769f8572..ac9eb7296d12e7bf5ab75c9db20ebaac58849c55 100644 (file)
@@ -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 */
index 8e9bfaa1edc5df55b381bfefd9550b83341fe5e5..78c45891fa49cb7c15a68ae8409b9cab09d1651f 100644 (file)
@@ -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);
index 8ce7e118a4fc2e336970d1f473271732960c16e9..3ac44b11cdc07006b88014225efdf0f8a4430732 100644 (file)
@@ -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;
index b8e55e57efa21ef6485d5116a9da361a82225a94..c6967251ce1f4132dd9cad90c9fcaf994e2e4621 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
  *     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);
index a1a61f710cf3c7cdd55511313350408f07584dbc..11b8c24c3d60b9e401c1f335e11af5549e4174be 100644 (file)
@@ -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"
index 5424731d4d0b0c96dbd31499d9b771a150c761e7..41c2f397882daf8d032f0dd12a6b74a4b867cf1b 100644 (file)
@@ -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
index 2ed67b3a1c6eab92118ffefad4f26f7fb115bfdb..d52d6c822e242e0de41d740cfbc9e94973560697 100644 (file)
@@ -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));
index 2b22d0cecd1d8c714adcb932a11a3f26cc7b97ca..16eee1e75456ae4e71e68a12701e2f6bd9ae75c3 100644 (file)
@@ -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);