OSDN Git Service

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 f5e15f1..9a9f9b5 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
  -->
      </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>
       <entry></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>
 
   <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 0479f57..193b817 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 b121084..a2b64c7 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 2041125..40591fd 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 1d0639b..d29a6df 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 52554c3..ae8965e 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 7c2cd4b..2213192 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 53249fa..ede6607 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,98 +564,11 @@ RemoveConstraintById(Oid conId)
        simple_heap_delete(conDesc, &tup->t_self);
 
        /* Clean up */
-       systable_endscan(conscan);
+       ReleaseSysCache(tup);
        heap_close(conDesc, RowExclusiveLock);
 }
 
 /*
- * GetConstraintNameForTrigger
- *             Get the name of the constraint owning a trigger, if any
- *
- * Returns a palloc'd string, or NULL if no constraint can be found
- */
-char *
-GetConstraintNameForTrigger(Oid triggerId)
-{
-       char       *result;
-       Oid                     constraintId = InvalidOid;
-       Relation        depRel;
-       Relation        conRel;
-       ScanKeyData key[2];
-       SysScanDesc scan;
-       HeapTuple       tup;
-
-       /*
-        * We must grovel through pg_depend to find the owning constraint. Perhaps
-        * pg_trigger should have a column for the owning constraint ... but right
-        * now this is not performance-critical code.
-        */
-       depRel = heap_open(DependRelationId, AccessShareLock);
-
-       ScanKeyInit(&key[0],
-                               Anum_pg_depend_classid,
-                               BTEqualStrategyNumber, F_OIDEQ,
-                               ObjectIdGetDatum(TriggerRelationId));
-       ScanKeyInit(&key[1],
-                               Anum_pg_depend_objid,
-                               BTEqualStrategyNumber, F_OIDEQ,
-                               ObjectIdGetDatum(triggerId));
-       /* assume we can ignore objsubid for a trigger */
-
-       scan = systable_beginscan(depRel, DependDependerIndexId, true,
-                                                         SnapshotNow, 2, key);
-
-       while (HeapTupleIsValid(tup = systable_getnext(scan)))
-       {
-               Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
-
-               if (foundDep->refclassid == ConstraintRelationId &&
-                       foundDep->deptype == DEPENDENCY_INTERNAL)
-               {
-                       constraintId = foundDep->refobjid;
-                       break;
-               }
-       }
-
-       systable_endscan(scan);
-
-       heap_close(depRel, AccessShareLock);
-
-       if (!OidIsValid(constraintId))
-               return NULL;                    /* no owning constraint found */
-
-       conRel = heap_open(ConstraintRelationId, AccessShareLock);
-
-       ScanKeyInit(&key[0],
-                               ObjectIdAttributeNumber,
-                               BTEqualStrategyNumber, F_OIDEQ,
-                               ObjectIdGetDatum(constraintId));
-
-       scan = systable_beginscan(conRel, ConstraintOidIndexId, true,
-                                                         SnapshotNow, 1, key);
-
-       tup = systable_getnext(scan);
-
-       if (HeapTupleIsValid(tup))
-       {
-               Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
-
-               result = pstrdup(NameStr(con->conname));
-       }
-       else
-       {
-               /* This arguably should be an error, but we'll just return NULL */
-               result = NULL;
-       }
-
-       systable_endscan(scan);
-
-       heap_close(conRel, AccessShareLock);
-
-       return result;
-}
-
-/*
  * AlterConstraintNamespaces
  *             Find any constraints belonging to the specified object,
  *             and move them to the specified new namespace.
index c11f337..6377945 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 349eadf..37cc0e6 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 ae98396..9ea32d7 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 001bc57..cea8dde 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 */
 }
 
@@ -1709,226 +1690,12 @@ renamerel(Oid myrelid, const char *newrelname)
                TypeRename(oldrelname, namespaceId, newrelname);
 
        /*
-        * Update rel name in any RI triggers associated with the relation.
-        */
-       if (relhastriggers)
-       {
-               /* update tgargs where relname is primary key */
-               update_ri_trigger_args(myrelid,
-                                                          oldrelname,
-                                                          newrelname,
-                                                          false, true);
-               /* update tgargs where relname is foreign key */
-               update_ri_trigger_args(myrelid,
-                                                          oldrelname,
-                                                          newrelname,
-                                                          true, true);
-       }
-
-       /*
         * Close rel, but keep exclusive lock!
         */
        relation_close(targetrelation, NoLock);
 }
 
 /*
- * Scan pg_trigger for RI triggers that are on the specified relation
- * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
- * is true).  Update RI trigger args fields matching oldname to contain
- * newname instead.  If update_relname is true, examine the relname
- * fields; otherwise examine the attname fields.
- */
-static void
-update_ri_trigger_args(Oid relid,
-                                          const char *oldname,
-                                          const char *newname,
-                                          bool fk_scan,
-                                          bool update_relname)
-{
-       Relation        tgrel;
-       ScanKeyData skey[1];
-       SysScanDesc trigscan;
-       HeapTuple       tuple;
-       Datum           values[Natts_pg_trigger];
-       char            nulls[Natts_pg_trigger];
-       char            replaces[Natts_pg_trigger];
-
-       tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
-       if (fk_scan)
-       {
-               ScanKeyInit(&skey[0],
-                                       Anum_pg_trigger_tgconstrrelid,
-                                       BTEqualStrategyNumber, F_OIDEQ,
-                                       ObjectIdGetDatum(relid));
-               trigscan = systable_beginscan(tgrel, TriggerConstrRelidIndexId,
-                                                                         true, SnapshotNow,
-                                                                         1, skey);
-       }
-       else
-       {
-               ScanKeyInit(&skey[0],
-                                       Anum_pg_trigger_tgrelid,
-                                       BTEqualStrategyNumber, F_OIDEQ,
-                                       ObjectIdGetDatum(relid));
-               trigscan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
-                                                                         true, SnapshotNow,
-                                                                         1, skey);
-       }
-
-       while ((tuple = systable_getnext(trigscan)) != NULL)
-       {
-               Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-               bytea      *val;
-               bytea      *newtgargs;
-               bool            isnull;
-               int                     tg_type;
-               bool            examine_pk;
-               bool            changed;
-               int                     tgnargs;
-               int                     i;
-               int                     newlen;
-               const char *arga[RI_MAX_ARGUMENTS];
-               const char *argp;
-
-               tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid);
-               if (tg_type == RI_TRIGGER_NONE)
-               {
-                       /* Not an RI trigger, forget it */
-                       continue;
-               }
-
-               /*
-                * It is an RI trigger, so parse the tgargs bytea.
-                *
-                * NB: we assume the field will never be compressed or moved out of
-                * line; so does trigger.c ...
-                */
-               tgnargs = pg_trigger->tgnargs;
-               val = DatumGetByteaP(fastgetattr(tuple,
-                                                                               Anum_pg_trigger_tgargs,
-                                                                               tgrel->rd_att, &isnull));
-               if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
-                       tgnargs > RI_MAX_ARGUMENTS)
-               {
-                       /* This probably shouldn't happen, but ignore busted triggers */
-                       continue;
-               }
-               argp = (const char *) VARDATA(val);
-               for (i = 0; i < tgnargs; i++)
-               {
-                       arga[i] = argp;
-                       argp += strlen(argp) + 1;
-               }
-
-               /*
-                * Figure out which item(s) to look at.  If the trigger is primary-key
-                * type and attached to my rel, I should look at the PK fields; if it
-                * is foreign-key type and attached to my rel, I should look at the FK
-                * fields.      But the opposite rule holds when examining triggers found
-                * by tgconstrrel search.
-                */
-               examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
-
-               changed = false;
-               if (update_relname)
-               {
-                       /* Change the relname if needed */
-                       i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
-                       if (strcmp(arga[i], oldname) == 0)
-                       {
-                               arga[i] = newname;
-                               changed = true;
-                       }
-               }
-               else
-               {
-                       /* Change attname(s) if needed */
-                       i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
-                               RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
-                       for (; i < tgnargs; i += 2)
-                       {
-                               if (strcmp(arga[i], oldname) == 0)
-                               {
-                                       arga[i] = newname;
-                                       changed = true;
-                               }
-                       }
-               }
-
-               if (!changed)
-               {
-                       /* Don't need to update this tuple */
-                       continue;
-               }
-
-               /*
-                * Construct modified tgargs bytea.
-                */
-               newlen = VARHDRSZ;
-               for (i = 0; i < tgnargs; i++)
-                       newlen += strlen(arga[i]) + 1;
-               newtgargs = (bytea *) palloc(newlen);
-               VARATT_SIZEP(newtgargs) = newlen;
-               newlen = VARHDRSZ;
-               for (i = 0; i < tgnargs; i++)
-               {
-                       strcpy(((char *) newtgargs) + newlen, arga[i]);
-                       newlen += strlen(arga[i]) + 1;
-               }
-
-               /*
-                * Build modified tuple.
-                */
-               for (i = 0; i < Natts_pg_trigger; i++)
-               {
-                       values[i] = (Datum) 0;
-                       replaces[i] = ' ';
-                       nulls[i] = ' ';
-               }
-               values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
-               replaces[Anum_pg_trigger_tgargs - 1] = 'r';
-
-               tuple = heap_modifytuple(tuple, RelationGetDescr(tgrel), values, nulls, replaces);
-
-               /*
-                * Update pg_trigger and its indexes
-                */
-               simple_heap_update(tgrel, &tuple->t_self, tuple);
-
-               CatalogUpdateIndexes(tgrel, tuple);
-
-               /*
-                * Invalidate trigger's relation's relcache entry so that other
-                * backends (and this one too!) are sent SI message to make them
-                * rebuild relcache entries.  (Ideally this should happen
-                * automatically...)
-                *
-                * We can skip this for triggers on relid itself, since that relcache
-                * flush will happen anyway due to the table or column rename.  We
-                * just need to catch the far ends of RI relationships.
-                */
-               pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-               if (pg_trigger->tgrelid != relid)
-                       CacheInvalidateRelcacheByRelid(pg_trigger->tgrelid);
-
-               /* free up our scratch memory */
-               pfree(newtgargs);
-               heap_freetuple(tuple);
-       }
-
-       systable_endscan(trigscan);
-
-       heap_close(tgrel, RowExclusiveLock);
-
-       /*
-        * Increment cmd counter to make updates visible; this is needed in case
-        * the same tuple has to be updated again by next pass (can happen in case
-        * of a self-referential FK relationship).
-        */
-       CommandCounterIncrement();
-}
-
-/*
  * AlterTable
  *             Execute ALTER TABLE, which can be a list of subcommands
  *
@@ -2552,7 +2319,8 @@ ATRewriteTables(List **wqueue)
 
                                refrel = heap_open(con->refrelid, RowShareLock);
 
-                               validateForeignKeyConstraint(fkconstraint, rel, refrel);
+                               validateForeignKeyConstraint(fkconstraint, rel, refrel,
+                                                                                        con->conid);
 
                                heap_close(refrel, NoLock);
                        }
@@ -4061,6 +3829,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
        Oid                     pktypoid[INDEX_MAX_KEYS];
        Oid                     fktypoid[INDEX_MAX_KEYS];
        Oid                     opclasses[INDEX_MAX_KEYS];
+       Oid                     pfeqoperators[INDEX_MAX_KEYS];
+       Oid                     ppeqoperators[INDEX_MAX_KEYS];
+       Oid                     ffeqoperators[INDEX_MAX_KEYS];
        int                     i;
        int                     numfks,
                                numpks;
@@ -4138,6 +3909,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
        MemSet(pktypoid, 0, sizeof(pktypoid));
        MemSet(fktypoid, 0, sizeof(fktypoid));
        MemSet(opclasses, 0, sizeof(opclasses));
+       MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
+       MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
+       MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
 
        numfks = transformColumnNameList(RelationGetRelid(rel),
                                                                         fkconstraint->fk_attrs,
@@ -4166,7 +3940,15 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
                                                                                   opclasses);
        }
 
-       /* Be sure referencing and referenced column types are comparable */
+       /*
+        * Look up the equality operators to use in the constraint.
+        *
+        * Note that we look for operator(s) with the PK type on the left; when
+        * the types are different this is the right choice because the PK index
+        * will need operators with the indexkey on the left.  Also, we take the
+        * PK type as being the declared input type of the opclass, which might be
+        * binary-compatible but not identical to the PK column type.
+        */
        if (numfks != numpks)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_FOREIGN_KEY),
@@ -4174,24 +3956,71 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 
        for (i = 0; i < numpks; i++)
        {
+               HeapTuple       cla_ht;
+               Form_pg_opclass cla_tup;
+               Oid                     amid;
+               Oid                     opfamily;
+               Oid                     pktype;
+               Oid                     fktype;
+               Oid                     pfeqop;
+               Oid                     ppeqop;
+               Oid                     ffeqop;
+               int16           eqstrategy;
+
+               /* We need several fields out of the pg_opclass entry */
+               cla_ht = SearchSysCache(CLAOID,
+                                                               ObjectIdGetDatum(opclasses[i]),
+                                                               0, 0, 0);
+               if (!HeapTupleIsValid(cla_ht))
+                       elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
+               cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+               amid = cla_tup->opcmethod;
+               opfamily = cla_tup->opcfamily;
+               pktype = cla_tup->opcintype;
+               ReleaseSysCache(cla_ht);
+
                /*
-                * pktypoid[i] is the primary key table's i'th key's type fktypoid[i]
-                * is the foreign key table's i'th key's type
-                *
-                * Note that we look for an operator with the PK type on the left;
-                * when the types are different this is critical because the PK index
-                * will need operators with the indexkey on the left. (Ordinarily both
-                * commutator operators will exist if either does, but we won't get
-                * the right answer from the test below on opclass membership unless
-                * we select the proper operator.)
+                * Check it's a btree; currently this can never fail since no other
+                * index AMs support unique indexes.  If we ever did have other
+                * types of unique indexes, we'd need a way to determine which
+                * operator strategy number is equality.  (Is it reasonable to
+                * insist that every such index AM use btree's number for equality?)
+                */
+               if (amid != BTREE_AM_OID)
+                       elog(ERROR, "only b-tree indexes are supported for foreign keys");
+               eqstrategy = BTEqualStrategyNumber;
+
+               /*
+                * There had better be a PK = PK operator for the index.
+                */
+               ppeqop = get_opfamily_member(opfamily, pktype, pktype, eqstrategy);
+
+               if (!OidIsValid(ppeqop))
+                       elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+                                eqstrategy, pktype, pktype, opfamily);
+
+               /*
+                * Are there equality operators that take exactly the FK type?
+                * Assume we should look through any domain here.
                 */
-               Operator        o = oper(NULL, list_make1(makeString("=")),
-                                                        pktypoid[i], fktypoid[i],
-                                                        true, -1);
+               fktype = getBaseType(fktypoid[i]);
+
+               pfeqop = get_opfamily_member(opfamily, pktype, fktype, eqstrategy);
+               ffeqop = get_opfamily_member(opfamily, fktype, fktype, eqstrategy);
 
-               if (o == NULL)
+               if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+               {
+                       /*
+                        * Otherwise, look for an implicit cast from the FK type to
+                        * the PK type, and if found, use the PK type's equality operator.
+                        */
+                       if (can_coerce_type(1, &fktype, &pktype, COERCION_IMPLICIT))
+                               pfeqop = ffeqop = ppeqop;
+               }
+
+               if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
                        ereport(ERROR,
-                                       (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
                                         errmsg("foreign key constraint \"%s\" "
                                                        "cannot be implemented",
                                                        fkconstraint->constr_name),
@@ -4202,41 +4031,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
                                                           format_type_be(fktypoid[i]),
                                                           format_type_be(pktypoid[i]))));
 
-               /*
-                * Check that the found operator is compatible with the PK index, and
-                * generate a warning if not, since otherwise costly seqscans will be
-                * incurred to check FK validity.
-                */
-               if (!op_in_opfamily(oprid(o), get_opclass_family(opclasses[i])))
-                       ereport(WARNING,
-                                       (errmsg("foreign key constraint \"%s\" "
-                                                       "will require costly sequential scans",
-                                                       fkconstraint->constr_name),
-                                        errdetail("Key columns \"%s\" and \"%s\" "
-                                                          "are of different types: %s and %s.",
-                                                          strVal(list_nth(fkconstraint->fk_attrs, i)),
-                                                          strVal(list_nth(fkconstraint->pk_attrs, i)),
-                                                          format_type_be(fktypoid[i]),
-                                                          format_type_be(pktypoid[i]))));
-
-               ReleaseSysCache(o);
-       }
-
-       /*
-        * Tell Phase 3 to check that the constraint is satisfied by existing rows
-        * (we can skip this during table creation).
-        */
-       if (!fkconstraint->skip_validation)
-       {
-               NewConstraint *newcon;
-
-               newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
-               newcon->name = fkconstraint->constr_name;
-               newcon->contype = CONSTR_FOREIGN;
-               newcon->refrelid = RelationGetRelid(pkrel);
-               newcon->qual = (Node *) fkconstraint;
-
-               tab->constraints = lappend(tab->constraints, newcon);
+               pfeqoperators[i] = pfeqop;
+               ppeqoperators[i] = ppeqop;
+               ffeqoperators[i] = ffeqop;
        }
 
        /*
@@ -4254,6 +4051,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
                                                                                                                 * constraint */
                                                                          RelationGetRelid(pkrel),
                                                                          pkattnum,
+                                                                         pfeqoperators,
+                                                                         ppeqoperators,
+                                                                         ffeqoperators,
                                                                          numpks,
                                                                          fkconstraint->fk_upd_action,
                                                                          fkconstraint->fk_del_action,
@@ -4269,6 +4069,24 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
        createForeignKeyTriggers(rel, fkconstraint, constrOid);
 
        /*
+        * Tell Phase 3 to check that the constraint is satisfied by existing rows
+        * (we can skip this during table creation).
+        */
+       if (!fkconstraint->skip_validation)
+       {
+               NewConstraint *newcon;
+
+               newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+               newcon->name = fkconstraint->constr_name;
+               newcon->contype = CONSTR_FOREIGN;
+               newcon->refrelid = RelationGetRelid(pkrel);
+               newcon->conid = constrOid;
+               newcon->qual = (Node *) fkconstraint;
+
+               tab->constraints = lappend(tab->constraints, newcon);
+       }
+
+       /*
         * Close pk table, but keep lock until we've committed.
         */
        heap_close(pkrel, NoLock);
@@ -4526,25 +4344,15 @@ transformFkeyCheckAttrs(Relation pkrel,
 static void
 validateForeignKeyConstraint(FkConstraint *fkconstraint,
                                                         Relation rel,
-                                                        Relation pkrel)
+                                                        Relation pkrel,
+                                                        Oid constraintOid)
 {
        HeapScanDesc scan;
        HeapTuple       tuple;
        Trigger         trig;
-       ListCell   *list;
-       int                     count;
 
        /*
-        * See if we can do it with a single LEFT JOIN query.  A FALSE result
-        * indicates we must proceed with the fire-the-trigger method.
-        */
-       if (RI_Initial_Check(fkconstraint, rel, pkrel))
-               return;
-
-       /*
-        * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
-        * if that tuple had just been inserted.  If any of those fail, it should
-        * ereport(ERROR) and that's that.
+        * Build a trigger call structure; we'll need it either way.
         */
        MemSet(&trig, 0, sizeof(trig));
        trig.tgoid = InvalidOid;
@@ -4552,35 +4360,23 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
        trig.tgenabled = TRUE;
        trig.tgisconstraint = TRUE;
        trig.tgconstrrelid = RelationGetRelid(pkrel);
+       trig.tgconstraint = constraintOid;
        trig.tgdeferrable = FALSE;
        trig.tginitdeferred = FALSE;
+       /* we needn't fill in tgargs */
 
-       trig.tgargs = (char **) palloc(sizeof(char *) *
-                                                                  (4 + list_length(fkconstraint->fk_attrs)
-                                                                       + list_length(fkconstraint->pk_attrs)));
-
-       trig.tgargs[0] = trig.tgname;
-       trig.tgargs[1] = RelationGetRelationName(rel);
-       trig.tgargs[2] = RelationGetRelationName(pkrel);
-       trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
-       count = 4;
-       foreach(list, fkconstraint->fk_attrs)
-       {
-               char       *fk_at = strVal(lfirst(list));
-
-               trig.tgargs[count] = fk_at;
-               count += 2;
-       }
-       count = 5;
-       foreach(list, fkconstraint->pk_attrs)
-       {
-               char       *pk_at = strVal(lfirst(list));
-
-               trig.tgargs[count] = pk_at;
-               count += 2;
-       }
-       trig.tgnargs = count - 1;
+       /*
+        * See if we can do it with a single LEFT JOIN query.  A FALSE result
+        * indicates we must proceed with the fire-the-trigger method.
+        */
+       if (RI_Initial_Check(&trig, rel, pkrel))
+               return;
 
+       /*
+        * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
+        * if that tuple had just been inserted.  If any of those fail, it should
+        * ereport(ERROR) and that's that.
+        */
        scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
 
        while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
@@ -4613,18 +4409,13 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
        }
 
        heap_endscan(scan);
-
-       pfree(trig.tgargs);
 }
 
 static void
 CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
-                                        ObjectAddress *constrobj, ObjectAddress *trigobj,
-                                        bool on_insert)
+                                        Oid constraintOid, bool on_insert)
 {
        CreateTrigStmt *fk_trigger;
-       ListCell   *fk_attr;
-       ListCell   *pk_attr;
 
        fk_trigger = makeNode(CreateTrigStmt);
        fk_trigger->trigname = fkconstraint->constr_name;
@@ -4649,32 +4440,9 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
        fk_trigger->deferrable = fkconstraint->deferrable;
        fk_trigger->initdeferred = fkconstraint->initdeferred;
        fk_trigger->constrrel = fkconstraint->pktable;
-
        fk_trigger->args = NIL;
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(fkconstraint->constr_name));
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(myRel->relname));
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(fkconstraint->pktable->relname));
-       fk_trigger->args = lappend(fk_trigger->args,
-                               makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-       if (list_length(fkconstraint->fk_attrs) != list_length(fkconstraint->pk_attrs))
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_FOREIGN_KEY),
-                                errmsg("number of referencing and referenced columns for foreign key disagree")));
-
-       forboth(fk_attr, fkconstraint->fk_attrs,
-                       pk_attr, fkconstraint->pk_attrs)
-       {
-               fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-               fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-       }
 
-       trigobj->objectId = CreateTrigger(fk_trigger, true);
-
-       /* Register dependency from trigger to constraint */
-       recordDependencyOn(trigobj, constrobj, DEPENDENCY_INTERNAL);
+       (void) CreateTrigger(fk_trigger, constraintOid);
 
        /* Make changes-so-far visible */
        CommandCounterIncrement();
@@ -4685,14 +4453,10 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
  */
 static void
 createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
-                                                Oid constrOid)
+                                                Oid constraintOid)
 {
        RangeVar   *myRel;
        CreateTrigStmt *fk_trigger;
-       ListCell   *fk_attr;
-       ListCell   *pk_attr;
-       ObjectAddress trigobj,
-                               constrobj;
 
        /*
         * Reconstruct a RangeVar for my relation (not passed in, unfortunately).
@@ -4700,15 +4464,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
        myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
                                                 pstrdup(RelationGetRelationName(rel)));
 
-       /*
-        * Preset objectAddress fields
-        */
-       constrobj.classId = ConstraintRelationId;
-       constrobj.objectId = constrOid;
-       constrobj.objectSubId = 0;
-       trigobj.classId = TriggerRelationId;
-       trigobj.objectSubId = 0;
-
        /* Make changes-so-far visible */
        CommandCounterIncrement();
 
@@ -4716,8 +4471,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
         * Build and execute a CREATE CONSTRAINT TRIGGER statement for the CHECK
         * action for both INSERTs and UPDATEs on the referencing table.
         */
-       CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, true);
-       CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, false);
+       CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, true);
+       CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, false);
 
        /*
         * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
@@ -4765,27 +4520,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
                                 (int) fkconstraint->fk_del_action);
                        break;
        }
-
        fk_trigger->args = NIL;
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(fkconstraint->constr_name));
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(myRel->relname));
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(fkconstraint->pktable->relname));
-       fk_trigger->args = lappend(fk_trigger->args,
-                               makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-       forboth(fk_attr, fkconstraint->fk_attrs,
-                       pk_attr, fkconstraint->pk_attrs)
-       {
-               fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-               fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-       }
-
-       trigobj.objectId = CreateTrigger(fk_trigger, true);
 
-       /* Register dependency from trigger to constraint */
-       recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+       (void) CreateTrigger(fk_trigger, constraintOid);
 
        /* Make changes-so-far visible */
        CommandCounterIncrement();
@@ -4835,49 +4572,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
                                 (int) fkconstraint->fk_upd_action);
                        break;
        }
-
        fk_trigger->args = NIL;
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(fkconstraint->constr_name));
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(myRel->relname));
-       fk_trigger->args = lappend(fk_trigger->args,
-                                                          makeString(fkconstraint->pktable->relname));
-       fk_trigger->args = lappend(fk_trigger->args,
-                               makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-       forboth(fk_attr, fkconstraint->fk_attrs,
-                       pk_attr, fkconstraint->pk_attrs)
-       {
-               fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-               fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-       }
-
-       trigobj.objectId = CreateTrigger(fk_trigger, true);
 
-       /* Register dependency from trigger to constraint */
-       recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
-}
-
-/*
- * fkMatchTypeToString -
- *       convert FKCONSTR_MATCH_xxx code to string to use in trigger args
- */
-static char *
-fkMatchTypeToString(char match_type)
-{
-       switch (match_type)
-       {
-               case FKCONSTR_MATCH_FULL:
-                       return pstrdup("FULL");
-               case FKCONSTR_MATCH_PARTIAL:
-                       return pstrdup("PARTIAL");
-               case FKCONSTR_MATCH_UNSPECIFIED:
-                       return pstrdup("UNSPECIFIED");
-               default:
-                       elog(ERROR, "unrecognized match type: %d",
-                                (int) match_type);
-       }
-       return NULL;                            /* can't get here */
+       (void) CreateTrigger(fk_trigger, constraintOid);
 }
 
 /*
index cc7dfc8..c08525c 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 4f88e95..99f964d 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 613ef65..5cabec2 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 c21d827..4ba2bb7 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;
@@ -108,10 +140,37 @@ typedef struct RI_QueryHashEntry
 
 
 /* ----------
+ * RI_CompareKey
+ *
+ *     The key identifying an entry showing how to compare two values
+ * ----------
+ */
+typedef struct RI_CompareKey
+{
+       Oid                     eq_opr;                 /* the equality operator to apply */
+       Oid                     typeid;                 /* the data type to apply it to */
+} RI_CompareKey;
+
+
+/* ----------
+ * RI_CompareHashEntry
+ * ----------
+ */
+typedef struct RI_CompareHashEntry
+{
+       RI_CompareKey key;
+       bool            valid;                          /* successfully initialized? */
+       FmgrInfo        eq_opr_finfo;           /* call info for equality fn */
+       FmgrInfo        cast_func_finfo;        /* in case we must coerce input */
+} RI_CompareHashEntry;
+
+
+/* ----------
  * Local data
  * ----------
  */
 static HTAB *ri_query_cache = NULL;
+static HTAB *ri_compare_cache = NULL;
 
 
 /* ----------
@@ -120,35 +179,41 @@ static HTAB *ri_query_cache = NULL;
  */
 static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
-static int     ri_DetermineMatchType(char *str);
+static void ri_GenerateQual(StringInfo buf,
+                                                       const char *sep,
+                                                       const char *leftop, Oid leftoptype,
+                                                       Oid opoid,
+                                                       const char *rightop, Oid rightoptype);
 static int ri_NullCheck(Relation rel, HeapTuple tup,
                         RI_QueryKey *key, int pairidx);
-static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id,
-                                        int32 constr_queryno,
-                                        Relation fk_rel, Relation pk_rel,
-                                        int argc, char **argv);
-static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id,
-                                               int32 constr_queryno,
-                                               Relation pk_rel,
-                                               int argc, char **argv);
+static void ri_BuildQueryKeyFull(RI_QueryKey *key,
+                                                                const RI_ConstraintInfo *riinfo,
+                                                                int32 constr_queryno);
+static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key,
+                                                                       const RI_ConstraintInfo *riinfo,
+                                                                       int32 constr_queryno);
 static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
-                        RI_QueryKey *key, int pairidx);
+                                                const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
-                                 RI_QueryKey *key, int pairidx);
-static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup,
-                          HeapTuple newtup, RI_QueryKey *key, int pairidx);
-static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);
+                                 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
+static bool ri_OneKeyEqual(Relation rel, int column,
+                          HeapTuple oldtup, HeapTuple newtup,
+                          const RI_ConstraintInfo *riinfo, bool rel_is_pk);
+static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
+                                                          Datum oldvalue, Datum newvalue);
 static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
                                  HeapTuple old_row,
-                                 Oid tgoid, int match_type,
-                                 int tgnargs, char **tgargs);
+                                 const RI_ConstraintInfo *riinfo);
 
 static void ri_InitHashTables(void);
 static void *ri_FetchPreparedPlan(RI_QueryKey *key);
 static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
 
 static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
                                int tgkind);
+static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
+                                          Trigger *trigger, Relation trig_rel, bool rel_is_pk);
 static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
                         RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
                         bool cache_plan);
@@ -176,8 +241,7 @@ static Datum
 RI_FKey_check(PG_FUNCTION_ARGS)
 {
        TriggerData *trigdata = (TriggerData *) fcinfo->context;
-       int                     tgnargs;
-       char      **tgargs;
+       RI_ConstraintInfo riinfo;
        Relation        fk_rel;
        Relation        pk_rel;
        HeapTuple       new_row;
@@ -186,7 +250,6 @@ RI_FKey_check(PG_FUNCTION_ARGS)
        RI_QueryKey qkey;
        void       *qplan;
        int                     i;
-       int                     match_type;
 
        /*
         * Check that this is a valid trigger call on the right time and event.
@@ -196,8 +259,9 @@ RI_FKey_check(PG_FUNCTION_ARGS)
        /*
         * Get arguments.
         */
-       tgnargs = trigdata->tg_trigger->tgnargs;
-       tgargs = trigdata->tg_trigger->tgargs;
+       ri_FetchConstraintInfo(&riinfo,
+                                                  trigdata->tg_trigger, trigdata->tg_relation, false);
+
        if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        {
                old_row = trigdata->tg_trigtuple;
@@ -237,7 +301,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
         * SELECT FOR SHARE will get on it.
         */
        fk_rel = trigdata->tg_relation;
-       pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+       pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
 
        /* ----------
         * SQL3 11.9 <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 3cd3173..064b8e0 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 8946cb7..d6ff883 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 5b33ff4..75f290d 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 2fec732..eef9a4c 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,7 +3841,7 @@ getTriggers(TableInfo tblinfo[], int numTables)
                selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
 
                resetPQExpBuffer(query);
-               if (g_fout->remoteVersion >= 70300)
+               if (g_fout->remoteVersion >= 80300)
                {
                        /*
                         * We ignore triggers that are tied to a foreign-key constraint
@@ -3855,6 +3855,25 @@ getTriggers(TableInfo tblinfo[], int numTables)
                                         "tgconstrrelid::pg_catalog.regclass as tgconstrrelname "
                                                          "from pg_catalog.pg_trigger t "
                                                          "where tgrelid = '%u'::pg_catalog.oid "
+                                                         "and tgconstraint = 0",
+                                                         tbinfo->dobj.catId.oid);
+               }
+               else if (g_fout->remoteVersion >= 70300)
+               {
+                       /*
+                        * We ignore triggers that are tied to a foreign-key constraint,
+                        * but in these versions we have to grovel through pg_constraint
+                        * to find out
+                        */
+                       appendPQExpBuffer(query,
+                                                         "SELECT tgname, "
+                                                         "tgfoid::pg_catalog.regproc as tgfname, "
+                                                         "tgtype, tgnargs, tgargs, tgenabled, "
+                                                         "tgisconstraint, tgconstrname, tgdeferrable, "
+                                                         "tgconstrrelid, tginitdeferred, tableoid, oid, "
+                                        "tgconstrrelid::pg_catalog.regclass as tgconstrrelname "
+                                                         "from pg_catalog.pg_trigger t "
+                                                         "where tgrelid = '%u'::pg_catalog.oid "
                                                          "and (not tgisconstraint "
                                                          " OR NOT EXISTS"
                                                          "  (SELECT 1 FROM pg_catalog.pg_depend d "
index 6469b6a..3e830d9 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 5bdda05..aec8dee 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 73e2cf0..baa650a 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 6fbfb6a..d01c22c 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
@@ -92,6 +92,24 @@ CATALOG(pg_constraint,2606)
        int2            confkey[1];
 
        /*
+        * If a foreign key, the OIDs of the PK = FK equality operators for each
+        * column of the constraint
+        */
+       Oid                     conpfeqop[1];
+
+       /*
+        * If a foreign key, the OIDs of the PK = PK equality operators for each
+        * column of the constraint (i.e., equality for the referenced columns)
+        */
+       Oid                     conppeqop[1];
+
+       /*
+        * If a foreign key, the OIDs of the FK = FK equality operators for each
+        * column of the constraint (i.e., equality for the referencing columns)
+        */
+       Oid                     conffeqop[1];
+
+       /*
         * If a check constraint, nodeToString representation of expression
         */
        text            conbin;
@@ -113,7 +131,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
  *             compiler constants for pg_constraint
  * ----------------
  */
-#define Natts_pg_constraint                                    15
+#define Natts_pg_constraint                                    18
 #define Anum_pg_constraint_conname                     1
 #define Anum_pg_constraint_connamespace                2
 #define Anum_pg_constraint_contype                     3
@@ -127,8 +145,11 @@ typedef FormData_pg_constraint *Form_pg_constraint;
 #define Anum_pg_constraint_confmatchtype       11
 #define Anum_pg_constraint_conkey                      12
 #define Anum_pg_constraint_confkey                     13
-#define Anum_pg_constraint_conbin                      14
-#define Anum_pg_constraint_consrc                      15
+#define Anum_pg_constraint_conpfeqop           14
+#define Anum_pg_constraint_conppeqop           15
+#define Anum_pg_constraint_conffeqop           16
+#define Anum_pg_constraint_conbin                      17
+#define Anum_pg_constraint_consrc                      18
 
 
 /* Valid values for contype */
@@ -167,6 +188,9 @@ extern Oid CreateConstraintEntry(const char *constraintName,
                                          Oid domainId,
                                          Oid foreignRelId,
                                          const int16 *foreignKey,
+                                         const Oid *pfEqOp,
+                                         const Oid *ppEqOp,
+                                         const Oid *ffEqOp,
                                          int foreignNKeys,
                                          char foreignUpdateType,
                                          char foreignDeleteType,
@@ -184,8 +208,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
                                         const char *label, Oid namespace,
                                         List *others);
 
-extern char *GetConstraintNameForTrigger(Oid triggerId);
-
 extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
                                                  Oid newNspId, bool isType);
 
index b540825..df22089 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 284815b..ac9eb72 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 8e9bfaa..78c4589 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 8ce7e11..3ac44b1 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 b8e55e5..c696725 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 a1a61f7..11b8c24 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 5424731..41c2f39 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 2ed67b3..d52d6c8 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 2b22d0c..16eee1e 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);